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

View File

@ -27,18 +27,17 @@ namespace RewriteSample
// AddFunctionalRule(Func);
// TODO make this startup do something useful.
app.UseRewriter(new RewriteOptions()
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
.ImportFromModRewrite(hostingEnv, "Rewrite.txt")
.RedirectToHttps(StatusCodes.Status307TemporaryRedirect)
.RewriteRule("/foo/(.*)/bar", "{R:1}/bar")
.AddRule(ctx =>
{
ctx.HttpContext.Request.Path = "/index";
return RuleResult.Continue;
}));
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
.ImportFromModRewrite(hostingEnv, "Rewrite.txt")
.RedirectToHttps(StatusCodes.Status307TemporaryRedirect)
.RewriteRule("/foo/(.*)/bar", "{R:1}/bar")
.AddRule(ctx =>
{
ctx.HttpContext.Request.Path = "/index";
return RuleResult.Continue;
}));
app.Run(context => context.Response.WriteAsync(context.Request.Path));
}
public static void Main(string[] args)

View File

@ -1,12 +1,8 @@
<rewrite>
<rules>
<rule name="Query String Rewrite">
<match url="page\.asp$" />
<conditions>
<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 name="Example" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://example.com/{R:1}" />
</rule>
</rules>
</rewrite>

View File

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

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

View File

@ -1,15 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public class Condition
{
public Pattern Input { get; set; }
public UrlMatch Match { get; set; }
public bool OrNext { get; set; }
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);
}
}

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;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
namespace Microsoft.AspNetCore.Rewrite.Internal
{
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 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>
/// <param name="condition">The CondPattern portion of a mod_rewrite RewriteCond.</param>
/// <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;
}
var context = new ParserContext(condition);
var results = new ParsedModRewriteExpression();
var results = new ParsedModRewriteInput();
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
@ -45,88 +45,90 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
results.Invert = true;
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
// Switch on current character
switch (context.Current)
{
case Greater:
if (!context.Next())
{
// Dangling ">"
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
}
if (context.Current == EqualSign)
{
if (!context.Next())
{
// Dangling ">="
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
}
results.Operation = OperationType.GreaterEqual;
results.Type = ConditionType.StringComp;
results.OperationType = OperationType.GreaterEqual;
results.ConditionType = ConditionType.StringComp;
}
else
{
results.Operation = OperationType.Greater;
results.Type = ConditionType.StringComp;
results.OperationType = OperationType.Greater;
results.ConditionType = ConditionType.StringComp;
}
break;
case Less:
if (!context.Next())
{
// Dangling "<"
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
}
if (context.Current == EqualSign)
{
if (!context.Next())
{
// Dangling "<="
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
}
results.Operation = OperationType.LessEqual;
results.Type = ConditionType.StringComp;
results.OperationType = OperationType.LessEqual;
results.ConditionType = ConditionType.StringComp;
}
else
{
results.Operation = OperationType.Less;
results.Type = ConditionType.StringComp;
results.OperationType = OperationType.Less;
results.ConditionType = ConditionType.StringComp;
}
break;
case EqualSign:
if (!context.Next())
{
// Dangling "="
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(condition, context.Index));
}
results.Operation = OperationType.Equal;
results.Type = ConditionType.StringComp;
results.OperationType = OperationType.Equal;
results.ConditionType = ConditionType.StringComp;
break;
case Dash:
results = ParseProperty(context, results.Invert);
if (results.Type == ConditionType.PropertyTest)
if (results.ConditionType == ConditionType.PropertyTest)
{
return results;
}
context.Next();
break;
default:
results.Type = ConditionType.Regex;
results.ConditionType = ConditionType.Regex;
break;
}
// Capture the rest of the string guarantee validity.
results.Operand = (condition.Substring(context.GetIndex()));
results.Operand = condition.Substring(context.GetIndex());
if (IsValidActionCondition(results))
{
return results;
}
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="invert"></param>
/// <returns></returns>
public static ParsedModRewriteExpression ParseProperty(ParserContext context, bool invert)
public static ParsedModRewriteInput ParseProperty(ParserContext context, bool invert)
{
if (!context.Next())
{
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
}
switch (context.Current)
{
case 'd':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Directory, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Directory, operand: null);
case 'f':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.RegularFile, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.RegularFile, operand: null);
case 'F':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingFile, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingFile, operand: null);
case 'h':
case 'L':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null);
case 's':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Size, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Size, operand: null);
case 'U':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, operand: null);
case 'x':
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Executable, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.Executable, operand: null);
case 'e':
if (!context.Next() || context.Current != 'q')
{
// 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':
if (!context.Next())
{
@ -175,47 +177,49 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
}
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')
{
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.GreaterEqual, null);
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.GreaterEqual, operand: null);
}
else
{
throw new FormatException(context.Error());
}
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())
{
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null);
return new ParsedModRewriteInput(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, operand: null);
}
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')
{
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.LessEqual, null);
return new ParsedModRewriteInput(invert, ConditionType.IntComp, OperationType.LessEqual, operand: null);
}
else
{
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
}
case 'n':
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:
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
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.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
///
/// </summary>
public static class FileParser
public class FileParser
{
public static List<Rule> Parse(TextReader input)
public List<Rule> Parse(TextReader input)
{
string line = null;
var rules = new List<Rule>();
var conditions = new List<Condition>();
// TODO consider passing Itokenizer and Ifileparser and provide implementations
var builder = new RuleBuilder();
var lineNum = 0;
while ((line = input.ReadLine()) != null)
{
lineNum++;
if (string.IsNullOrEmpty(line))
{
continue;
@ -33,71 +30,65 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (tokens.Count > 4)
{
// 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])
{
case "RewriteBase":
throw new NotSupportedException();
//if (tokens.Count == 2)
//{
// ModRewriteBase.Base = tokens[1];
//}
//else
//{
// throw new FormatException("");
//}
//break;
throw new NotImplementedException("RewriteBase is not implemented");
case "RewriteCond":
try
{
ConditionBuilder builder = null;
if (tokens.Count == 3)
var pattern = TestStringParser.Parse(tokens[1]);
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 = new ConditionBuilder(tokens[1], tokens[2], tokens[3]);
}
else
{
throw new FormatException();
}
conditions.Add(builder.Build());
break;
builder.AddConditionFromParts(pattern, condActionParsed, flags);
}
catch (FormatException formatException)
{
throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException);
}
break;
case "RewriteRule":
try
{
RuleBuilder builder = null;
if (tokens.Count == 3)
var regex = RuleRegexParser.ParseRuleRegex(tokens[1]);
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 = new RuleBuilder(tokens[1], tokens[2], tokens[3]);
}
else
{
throw new FormatException();
}
builder.AddConditions(conditions);
builder.AddMatch(regex, flags);
builder.AddAction(pattern, flags);
rules.Add(builder.Build());
conditions = new List<Condition>();
break;
builder = new RuleBuilder();
}
catch (FormatException formatException)
{
throw new FormatException(Resources.FormatError_ModRewriteGeneralParseError(lineNum), formatException);
}
break;
case "RewriteMap":
throw new NotImplementedException("RewriteMaps to be added soon.");
throw new NotImplementedException("RewriteMap to be added soon.");
case "RewriteEngine":
// Explicitly do nothing here, no notion of turning on regex engine.
break;
default:
throw new FormatException(tokens[0]);
throw new FormatException(Resources.FormatError_ModRewriteParseError("Unrecognized keyword: " + tokens[0], lineNum));
}
}
return rules;
}
}
}

View File

@ -5,99 +5,38 @@ using System;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// Parses the flags
/// </summary>
public class FlagParser
{
// TODO Refactor Rule and Condition Flags under IFlags
public static RuleFlags ParseRuleFlags(string flagString)
{
var flags = new RuleFlags();
ParseRuleFlags(flagString, flags);
return flags;
}
public static void ParseRuleFlags(string flagString, RuleFlags flags)
{
public static Flags Parse(string flagString)
{
if (string.IsNullOrEmpty(flagString))
{
return;
return null;
}
// Check that flags are contained within []
if (!flagString.StartsWith("[") || !flagString.EndsWith("]"))
{
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.
// Illegal syntax to have any spaces.
// Invalid 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 '='.
var flags = new Flags();
foreach (string token in tokens)
{
if (string.IsNullOrEmpty(token))
var hasPayload = token.Split('=');
if (hasPayload.Length == 2)
{
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]);
flags.SetFlag(hasPayload[0], hasPayload[1]);
}
else
{
throw new FormatException();
flags.SetFlag(hasPayload[0], string.Empty);
}
}
return flags;
}
}
}

View File

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

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
{
public class ParsedModRewriteExpression
public class ParsedModRewriteInput
{
public bool Invert { get; set; }
public ConditionType Type { get; set; }
public OperationType Operation { get; set; }
public ConditionType ConditionType { get; set; }
public OperationType OperationType { 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;
Type = type;
Operation = operation;
ConditionType = conditionType;
OperationType = operationType;
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.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
{
public class RuleBuilder
{
private ParsedModRewriteExpression _pce;
private List<Condition> _conditions;
private RuleFlags _flags;
private Pattern _patterns;
private Conditions _conditions;
private UrlAction _action;
private UrlMatch _match;
private List<PreAction> _preActions;
private readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
public ModRewriteRule Build()
{
var ruleExpression = ExpressionCreator.CreateRuleExpression(_pce, _flags);
return new ModRewriteRule(_conditions, ruleExpression, _patterns, _flags);
if (_action == null || _match == null)
{
// TODO throw an exception here, find apporpriate exception
}
return new ModRewriteRule(_match, _conditions, _action, _preActions);
}
public RuleBuilder(string initialRule, string transformation) :
this(initialRule, transformation, flags: null)
{
}
public RuleBuilder(string rule)
public void AddRule(string rule)
{
// TODO
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
{
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);
}
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)
if (flags.HasFlag(FlagType.NoCase))
{
_conditions = new List<Condition>();
}
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);
_match = new RegexMatch(new Regex(input.Operand, RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexTimeout), input.Invert);
}
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 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("!"))
{
return new ParsedModRewriteExpression { Invert = true, Operand = regex.Substring(1) };
return new ParsedModRewriteInput { Invert = true, Operand = regex.Substring(1) };
}
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.
// 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
{

View File

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

View File

@ -4,13 +4,14 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
/// <summary>
/// Parses the TestString segment of the mod_rewrite condition.
/// </summary>
public class ConditionTestStringParser
public class TestStringParser
{
private const char Percent = '%';
private const char Dollar = '$';
@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// %1
/// $1</param>
/// <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)
{
@ -45,12 +47,9 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// This is a server parameter, parse for a condition variable
if (!context.Next())
{
throw new FormatException(context.Error());
}
if (!ParseConditionParameter(context, results))
{
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index));
}
ParseConditionParameter(context, results);
}
else if (context.Current == Dollar)
{
@ -58,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
// and create a new Pattern Segment.
if (!context.Next())
{
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index));
}
context.Mark();
if (context.Current >= '0' && context.Current <= '9')
@ -66,21 +65,24 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
context.Next();
var ruleVariable = context.Capture();
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
{
throw new FormatException(context.Error());
throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index));
}
}
else
{
// Parse for literals, which will return on either the end of the test string
// or when it hits a special character
if (!ParseLiteral(context, results))
{
throw new FormatException(context.Error());
}
ParseLiteral(context, results);
}
}
return new Pattern(results);
@ -95,7 +97,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <param name="context">The ParserContext</param>
/// <param name="results">The List of results which the new condition parameter will be added.</param>
/// <returns>true </returns>
private static bool ParseConditionParameter(ParserContext context, List<PatternSegment> results)
private static void ParseConditionParameter(ParserContext context, List<PatternSegment> results)
{
// Parse { }
if (context.Current == OpenBrace)
@ -104,34 +106,26 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (!context.Next())
{
// Dangling {
return false;
throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index));
}
context.Mark();
while (context.Current != CloseBrace)
{
if (!context.Next())
{
// No closing } for the server variable
return false;
throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index));
}
else if (context.Current == Colon)
{
// Have a segmented look up Ex: HTTP:xxxx
// TODO
throw new NotImplementedException("Segmented Lookups no implemented");
}
}
// Need to verify server variable captured exists
var rawServerVariable = context.Capture();
if (IsValidServerVariable(rawServerVariable))
{
results.Add(new PatternSegment(rawServerVariable, SegmentType.ServerParameter));
}
else
{
// invalid.
return false;
}
results.Add(ServerVariables.FindServerVariable(rawServerVariable, context));
}
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
// capture is exclusive, we need to go one past the end index, capture, and then go 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
{
// illegal escape of a character
return false;
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(context.Template, context.Index));
}
return true;
}
/// <summary>
@ -159,7 +157,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
/// <param name="context"></param>
/// <param name="results"></param>
/// <returns></returns>
private static bool ParseLiteral(ParserContext context, List<PatternSegment> results)
private static void ParseLiteral(ParserContext context, List<PatternSegment> results)
{
context.Mark();
string literal;
@ -177,29 +175,8 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
break;
}
}
if (IsValidLiteral(context, 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);
// add results
results.Add(new LiteralSegment(literal));
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
{
@ -50,7 +49,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
if (!context.Next())
{
// 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)

View File

@ -1,208 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.Net.Http.Headers;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public class ModRewriteRule : Rule
{
public List<Condition> Conditions { get; set; } = new List<Condition>();
public string Description { get; set; } = string.Empty;
public RuleExpression InitialRule { get; set; }
public Pattern Transform { get; set; }
public RuleFlags Flags { get; set; } = new RuleFlags();
public ModRewriteRule() { }
public UrlMatch InitialMatch { get; set; }
public Conditions Conditions { get; set; }
public UrlAction Action { get; set; }
public List<PreAction> PreActions { get; set; }
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;
InitialRule = initialRule;
Transform = transforms;
Flags = flags;
Description = description;
InitialMatch = initialMatch;
Action = urlAction;
PreActions = preActions;
}
public override RuleResult ApplyRule(RewriteContext context)
{
// 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 (CheckMatchResult(results.Success))
if (!initMatchRes.Success)
{
return RuleResult.Continue;
}
if (Flags.HasFlag(RuleFlagType.EscapeBackreference))
MatchResults condMatchRes = null;
if (Conditions != null)
{
// TODO Escape Backreferences here.
}
// 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
condMatchRes = Conditions.Evaluate(context, initMatchRes);
if (!condMatchRes.Success)
{
return RuleResult.Continue;
}
}
}
private bool CheckMatchResult(bool? result)
{
if (result == null)
// 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
foreach (var preAction in PreActions)
{
return false;
}
return !(result.Value ^ InitialRule.Invert);
}
private bool CheckCondition(RewriteContext context, Match results, Match previous)
{
if (Conditions == null)
{
return true;
preAction.ApplyAction(context.HttpContext, initMatchRes, condMatchRes);
}
// TODO Visitor pattern here?
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;
return Action.ApplyAction(context, initMatchRes, condMatchRes);
}
}
}

View File

@ -8,19 +8,19 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
/// </summary>
public class ParserContext
{
private readonly string _template;
public readonly string Template;
public int Index { get; set; }
private int? _mark;
public ParserContext(string condition)
{
_template = condition;
Template = condition;
Index = -1;
}
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()
@ -30,12 +30,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
public bool Next()
{
return ++Index < _template.Length;
return ++Index < Template.Length;
}
public bool HasNext()
{
return (Index + 1) < _template.Length;
return (Index + 1) < Template.Length;
}
public void Mark()
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal
// TODO make this return a range rather than a string.
if (_mark.HasValue)
{
var value = _template.Substring(_mark.Value, Index - _mark.Value);
var value = Template.Substring(_mark.Value, Index - _mark.Value);
_mark = null;
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.
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
{
@ -15,16 +13,16 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
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
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.
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
{
// 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.
// 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
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
{
public class ConditionMatchSegment : PatternSegment
{
@ -14,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
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;
}

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

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.
// 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
{
@ -14,9 +14,14 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
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);
context.Builder = tempBuilder;
return pattern.ToLowerInvariant();
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
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
{
@ -15,9 +15,12 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.PatternSegments
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);
context.Builder = tempBuilder;
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.
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 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.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{
public class RedirectAction : UrlAction
{
@ -16,11 +15,11 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
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);
context.Response.StatusCode = StatusCode;
context.HttpContext.Response.StatusCode = StatusCode;
// url can either contain the full url or the path and query
// always add to location header.
@ -28,13 +27,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
var split = pattern.IndexOf('?');
if (split >= 0)
{
var query = context.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
// not using the response.redirect here because status codes may be 301, 302, 307, 308
context.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
var query = context.HttpContext.Request.QueryString.Add(new QueryString(pattern.Substring(split)));
// not using the HttpContext.Response.redirect here because status codes may be 301, 302, 307, 308
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern.Substring(0, split) + query;
}
else
{
context.Response.Headers[HeaderNames.Location] = pattern;
context.HttpContext.Response.Headers[HeaderNames.Location] = pattern;
}
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.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlActions
{
public class RedirectClearQueryAction : UrlAction
{
@ -16,13 +15,13 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions
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);
context.Response.StatusCode = StatusCode;
context.HttpContext.Response.StatusCode = StatusCode;
// 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;
}
}

View File

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

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
namespace Microsoft.AspNetCore.Rewrite.Internal
{
public abstract class UrlMatch
{

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
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.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 IntegerOperationType Operation { get; }
public IntegerOperand(int value, IntegerOperationType operation)
public IntegerMatch(int value, IntegerOperationType operation)
{
Value = value;
Operation = operation;
}
public IntegerOperand(string value, IntegerOperationType operation)
public IntegerMatch(string value, IntegerOperationType operation)
{
int compValue;
if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue))
@ -29,27 +27,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
Operation = operation;
}
public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider)
public override MatchResults Evaluate(string input, RewriteContext context)
{
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)
{
case IntegerOperationType.Equal:
return compValue == Value;
return compValue == Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Greater:
return compValue > Value;
return compValue > Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.GreaterEqual:
return compValue >= Value;
return compValue >= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.Less:
return compValue < Value;
return compValue < Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.LessEqual:
return compValue <= Value;
return compValue <= Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
case IntegerOperationType.NotEqual:
return compValue != Value;
return compValue != Value ? MatchResults.EmptySuccess : MatchResults.EmptyFailure;
default:
return null;
}

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.ModRewrite.Operands
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
public enum IntegerOperationType
{

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
public class IsDirectoryMatch : UrlMatch
{

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
public class IsFileMatch : UrlMatch
{

View File

@ -4,7 +4,7 @@
using System;
using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlMatches
{
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.
// 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
{

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

View File

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

View File

@ -5,8 +5,9 @@ using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite.UrlMatches;
using Microsoft.AspNetCore.Rewrite.Internal;
using Microsoft.AspNetCore.Rewrite.Internal.UrlActions;
using Microsoft.AspNetCore.Rewrite.Internal.UrlMatches;
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{
@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
private UrlMatch _initialMatch;
private Conditions _conditions;
private UrlAction _action;
private bool _matchAny;
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.
public void AddUrlCondition(Pattern input, string pattern, PatternSyntax patternSyntax, MatchType matchType, bool ignoreCase, bool negate)
{
// If there are no conditions specified,
if (_conditions == null)
{
AddUrlConditions(LogicalGrouping.MatchAll, trackingAllCaptures: false);
}
switch (patternSyntax)
{
case PatternSyntax.ECMAScript:
@ -123,17 +127,17 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
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;
}
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;
}
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;
}
default:
@ -149,7 +153,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{
throw new FormatException("Match does not have an associated pattern attribute in condition");
}
_conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate) });
_conditions.ConditionList.Add(new Condition { Input = input, Match = new ExactMatch(ignoreCase, pattern, negate), OrNext = _matchAny });
break;
default:
throw new FormatException("Unrecognized pattern syntax");
@ -160,8 +164,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
{
var conditions = new Conditions();
conditions.ConditionList = new List<Condition>();
conditions.MatchType = logicalGrouping;
conditions.TrackingAllCaptures = trackingAllCaptures;
_matchAny = logicalGrouping == LogicalGrouping.MatchAny;
_conditions = conditions;
}
}

View File

@ -1,7 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Rewrite.Internal.UrlRewrite
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.Internal;
namespace Microsoft.AspNetCore.Rewrite.Internal
{
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.
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);
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;
}
@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Rewrite
{
throw new ArgumentNullException(nameof(reader));
}
options.Rules.AddRange(FileParser.Parse(reader));
options.Rules.AddRange(new FileParser().Parse(reader));
return options;
}
@ -79,7 +79,8 @@ namespace Microsoft.AspNetCore.Rewrite
throw new ArgumentNullException(nameof(rule));
}
var builder = new RuleBuilder(rule);
var builder = new RuleBuilder();
builder.AddRule(rule);
options.Rules.Add(builder.Build());
return options;
}

View File

@ -106,6 +106,38 @@ namespace Microsoft.AspNetCore.Rewrite
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)
{
var value = _resourceManager.GetString(name);

View File

@ -135,4 +135,10 @@
<data name="Error_InputParserUnrecognizedParameter" xml:space="preserve">
<value>Unrecognized parameter type: '{0}', terminated at string index: '{1}'</value>
</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>

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
@ -13,5 +14,7 @@ namespace Microsoft.AspNetCore.Rewrite
{
public HttpContext HttpContext { 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 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));
}
@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
var 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));
}
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{
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));
}
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{
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));
}
@ -79,15 +79,15 @@ namespace Microsoft.AspNetCore.Rewrite.Tests.ModRewrite
{
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));
}
// TODO negative tests
private bool CompareConditions(ParsedModRewriteExpression i1, ParsedModRewriteExpression i2)
private bool CompareConditions(ParsedModRewriteInput i1, ParsedModRewriteInput i2)
{
if (i1.Operation != i2.Operation ||
i1.Type != i2.Type ||
if (i1.OperationType != i2.OperationType ||
i1.ConditionType != i2.ConditionType ||
i1.Operand != i2.Operand ||
i1.Invert != i2.Invert)
{

View File

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