IIS UrlRewrite parsing.
This commit is contained in:
parent
4687aad61e
commit
a62e327c23
|
|
@ -13,6 +13,7 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99B72A07-32D6-434D-B44D-D064E3C13E08}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
global.json = global.json
|
||||
NuGetPackageVerifier.json = NuGetPackageVerifier.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Buffering", "src\Microsoft.AspNetCore.Buffering\Microsoft.AspNetCore.Buffering.xproj", "{2363D0DD-A3BF-437E-9B64-B33AE132D875}"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
],
|
||||
"packages": {
|
||||
"Microsoft.AspNetCore.Buffering": { },
|
||||
"Microsoft.AspNetCore.HttpOverrides": { }
|
||||
"Microsoft.AspNetCore.HttpOverrides": { },
|
||||
"Microsoft.AspNetCore.Rewrite": { }
|
||||
}
|
||||
},
|
||||
"Default": { // Rules to run for packages not listed in any other set.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
# Ensure Https
|
||||
RewriteCond %{REQUEST_URI} ^foo/
|
||||
RewriteCond %{HTTPS} off
|
||||
# U is a new flag to represent full URL rewrites
|
||||
RewriteRule ^(.*)$ https://www.example.com$1 [L,U]
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ namespace RewriteSample
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment hostingEnv)
|
||||
{
|
||||
app.UseRewriter(new UrlRewriteOptions()
|
||||
.ImportFromModRewrite("Rewrite.txt"));
|
||||
.ImportFromUrlRewrite(hostingEnv, "UrlRewrite.xml")
|
||||
.ImportFromModRewrite(hostingEnv, "Rewrite.txt"));
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Fail bad requests">
|
||||
<match url=".*"/>
|
||||
<conditions>
|
||||
<add input="{HTTP_HOST}" pattern="localhost" negate="true" />
|
||||
</conditions>
|
||||
<action type="AbortRequest" />
|
||||
</rule>
|
||||
<rule name="Redirect from blog">
|
||||
<match url="^blog/([_0-9a-z-]+)/([0-9]+)" />
|
||||
<action type="Redirect" url="article/{R:2}/{R:1}" redirectType="Found" />
|
||||
</rule>
|
||||
<rule name="Rewrite to article.aspx">
|
||||
<match url="^article/([0-9]+)/([_0-9a-z-]+)" />
|
||||
<action type="Rewrite" url="article.aspx?id={R:1}&title={R:2}" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
|
|
@ -13,17 +14,27 @@ namespace Microsoft.AspNetCore.Rewrite
|
|||
/// Imports rules from a mod_rewrite file and adds the rules to current rules.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="hostingEnv"></param>
|
||||
/// <param name="filePath">The path to the file containing mod_rewrite rules.</param>
|
||||
public static UrlRewriteOptions ImportFromModRewrite(this UrlRewriteOptions options, string filePath)
|
||||
public static UrlRewriteOptions ImportFromModRewrite(this UrlRewriteOptions options, IHostingEnvironment hostingEnv, string filePath)
|
||||
{
|
||||
// TODO use IHostingEnvironment as param.
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException("UrlRewriteOptions is null");
|
||||
}
|
||||
|
||||
if (hostingEnv == null)
|
||||
{
|
||||
throw new ArgumentNullException("HostingEnvironment is null");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException(nameof(filePath));
|
||||
}
|
||||
// TODO IHosting to fix!
|
||||
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
var path = Path.Combine(hostingEnv.ContentRootPath, filePath);
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
options.Rules.AddRange(FileParser.Parse(new StreamReader(stream)));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using System.Text.RegularExpressions;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains a sequence of pattern segments, which on obtaining the context, will create the appropriate
|
||||
|
|
|
|||
|
|
@ -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.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// A Pattern segment contains a portion of the test string/ substitution segment with a type associated.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Rewrite.Operands
|
|||
{
|
||||
throw new FormatException("Syntax error for integers in comparison.");
|
||||
}
|
||||
Value = compValue;
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public enum ActionType
|
||||
{
|
||||
None,
|
||||
Rewrite,
|
||||
Redirect,
|
||||
CustomResponse,
|
||||
AbortRequest
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public class Condition
|
||||
{
|
||||
public Pattern Input { get; set; }
|
||||
public Regex MatchPattern { get; set; }
|
||||
public bool Negate { get; set; }
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
public MatchType MatchType { get; set; } = MatchType.Pattern;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public class InitialMatch
|
||||
{
|
||||
public Regex Url { get; set; } // TODO must be a non-empty string, throw in check after parsing?
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
public bool Negate { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
// 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 System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Rewrite.Internal;
|
||||
using Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public class InputParser
|
||||
{
|
||||
private const char Colon = ':';
|
||||
private const char OpenBrace = '{';
|
||||
private const char CloseBrace = '}';
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pattern, which is a template to create a new test string to
|
||||
/// compare to the condition. Can contain server variables, back references, etc.
|
||||
/// </summary>
|
||||
/// <param name="testString"></param>
|
||||
/// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns>
|
||||
public static Pattern ParseInputString(string testString)
|
||||
{
|
||||
if (testString == null)
|
||||
{
|
||||
testString = string.Empty;
|
||||
}
|
||||
|
||||
var context = new ParserContext(testString);
|
||||
return ParseString(context);
|
||||
}
|
||||
|
||||
private static Pattern ParseString(ParserContext context)
|
||||
{
|
||||
var results = new List<PatternSegment>();
|
||||
while (context.Next())
|
||||
{
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// This is a server parameter, parse for a condition variable
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
ParseParameter(context, results);
|
||||
}
|
||||
else if (context.Current == CloseBrace)
|
||||
{
|
||||
// TODO we should be throwing a syntax error if we have uneven close braces
|
||||
// Can fix by keeping track of the number of '{' and '}' with an int, where {
|
||||
// increments and } decrements. Throw if < 0.
|
||||
return new Pattern(results);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse for literals, which will return on either the end of the test string
|
||||
// or when it hits a special character
|
||||
ParseLiteral(context, results);
|
||||
}
|
||||
}
|
||||
return new Pattern(results);
|
||||
}
|
||||
|
||||
private static void ParseParameter(ParserContext context, List<PatternSegment> results)
|
||||
{
|
||||
context.Mark();
|
||||
// Four main cases:
|
||||
// 1. {NAME} - Server Variable, create lambda to get the part of the context
|
||||
// 2. {R:1} - Rule parameter
|
||||
// 3. {C:1} - Condition Parameter
|
||||
// 4. {function:xxx} - String function
|
||||
// TODO consider perf here. This is on startup and will only happen one time
|
||||
// (unless we support Reload)
|
||||
string parameter;
|
||||
while (context.Next())
|
||||
{
|
||||
if (context.Current == CloseBrace)
|
||||
{
|
||||
// This is just a server variable, so we do a lookup and verify the server variable exists.
|
||||
parameter = context.Capture();
|
||||
results.Add(ServerVariables.FindServerVariable(parameter));
|
||||
return;
|
||||
}
|
||||
else if (context.Current == Colon)
|
||||
{
|
||||
parameter = context.Capture();
|
||||
|
||||
// Only 5 strings to expect here. Case sensitive.
|
||||
switch (parameter)
|
||||
{
|
||||
case "ToLower":
|
||||
{
|
||||
var pattern = ParseString(context);
|
||||
results.Add(new ToLowerSegment(pattern));
|
||||
|
||||
// at this point, we expect our context to be on the ending closing brace,
|
||||
// because the ParseString() call will increment the context until it
|
||||
// has processed the new string.
|
||||
if (context.Current != CloseBrace)
|
||||
{
|
||||
throw new FormatException("Lacking close brace for parameter at index: " + context.GetIndex());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "UrlDecode":
|
||||
{
|
||||
throw new NotImplementedException("UrlDecode is not supported.");
|
||||
}
|
||||
case "UrlEncode":
|
||||
{
|
||||
var pattern = ParseString(context);
|
||||
results.Add(new UrlEncodeSegment(pattern));
|
||||
|
||||
if (context.Current != CloseBrace)
|
||||
{
|
||||
throw new FormatException("Lacking close brace for parameter at index: " + context.GetIndex());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "R":
|
||||
{
|
||||
var index = GetBackReferenceIndex(context);
|
||||
results.Add(new RuleMatchSegment(index));
|
||||
return;
|
||||
}
|
||||
case "C":
|
||||
{
|
||||
var index = GetBackReferenceIndex(context);
|
||||
results.Add(new ConditionMatchSegment(index));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new FormatException("Unrecognized parameter type: " + parameter + ", terminated at index: " + context.GetIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetBackReferenceIndex(ParserContext context)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException("No index avaible for backreference at index: " + context.GetIndex());
|
||||
}
|
||||
|
||||
context.Mark();
|
||||
while (context.Current != CloseBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException("Lacking close brace for parameter at index: " + context.GetIndex());
|
||||
}
|
||||
}
|
||||
|
||||
var res = context.Capture();
|
||||
int index;
|
||||
if (!int.TryParse(res, out index))
|
||||
{
|
||||
throw new FormatException("Syntax error, invalid integer in response parameter at index: " + context.GetIndex());
|
||||
}
|
||||
|
||||
if (index > 9 || index < 0)
|
||||
{
|
||||
throw new FormatException("Invalid integer into backreference " + index + "at index: " + context.GetIndex());
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private static bool ParseLiteral(ParserContext context, List<PatternSegment> results)
|
||||
{
|
||||
context.Mark();
|
||||
string literal;
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == OpenBrace || context.Current == CloseBrace)
|
||||
{
|
||||
literal = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!context.Next())
|
||||
{
|
||||
literal = context.Capture();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(new LiteralSegment(literal));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// 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.UrlRewrite
|
||||
{
|
||||
public enum LogicalGrouping
|
||||
{
|
||||
MatchAll,
|
||||
MatchAny
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.UrlRewrite
|
||||
{
|
||||
public enum MatchType
|
||||
{
|
||||
Pattern,
|
||||
IsFile,
|
||||
IsDirectory
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public class Pattern
|
||||
{
|
||||
public IList<PatternSegment> PatternSegments { get; }
|
||||
|
||||
public Pattern(List<PatternSegment> patternSegments)
|
||||
{
|
||||
PatternSegments = patternSegments;
|
||||
}
|
||||
|
||||
public string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
var strBuilder = new StringBuilder();
|
||||
foreach (var pattern in PatternSegments)
|
||||
{
|
||||
strBuilder.Append(pattern.Evaluate(context, ruleMatch, condMatch));
|
||||
}
|
||||
return strBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public abstract class PatternSegment
|
||||
{
|
||||
// Match from prevRule, Match from prevCond
|
||||
public abstract string Evaluate(HttpContext context, Match ruleMatch, Match condMatch);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class ConditionMatchSegment : PatternSegment
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
public ConditionMatchSegment(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return condMatch?.Groups[Index]?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class HeaderSegment : PatternSegment
|
||||
{
|
||||
public string Header { get; set; }
|
||||
|
||||
public HeaderSegment(string header)
|
||||
{
|
||||
Header = header;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Request.Headers[Header];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class IsHttpsSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Request.IsHttps ? "ON" : "OFF";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class LiteralSegment : PatternSegment
|
||||
{
|
||||
public string Literal { get; set; }
|
||||
|
||||
public LiteralSegment(string literal)
|
||||
{
|
||||
Literal = literal;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return Literal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class LocalAddressSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Connection.LocalIpAddress?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class QueryStringSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Request.QueryString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class RemoteAddressSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Connection.RemoteIpAddress?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class RemotePortSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class RuleMatchSegment : PatternSegment
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
public RuleMatchSegment(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return ruleMatch?.Groups[Index]?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class ToLowerSegment : PatternSegment
|
||||
{
|
||||
public Pattern Pattern { get; set; }
|
||||
|
||||
public ToLowerSegment(Pattern pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
var pattern = Pattern.Evaluate(context, ruleMatch, condMatch);
|
||||
return pattern.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.Encodings.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class UrlEncodeSegment : PatternSegment
|
||||
{
|
||||
public Pattern Pattern { get; set; }
|
||||
|
||||
public UrlEncodeSegment(Pattern pattern)
|
||||
{
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
var pattern = Pattern.Evaluate(context, ruleMatch, condMatch);
|
||||
return UrlEncoder.Default.Encode(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments
|
||||
{
|
||||
public class UrlSegment : PatternSegment
|
||||
{
|
||||
public override string Evaluate(HttpContext context, Match ruleMatch, Match condMatch)
|
||||
{
|
||||
return context.Request.Path.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.UrlRewrite
|
||||
{
|
||||
public enum PatternSyntax
|
||||
{
|
||||
ECMAScript,
|
||||
WildCard,
|
||||
ExactMatch
|
||||
}
|
||||
}
|
||||
|
|
@ -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.UrlRewrite
|
||||
{
|
||||
public enum RedirectType
|
||||
{
|
||||
Permanent = 301,
|
||||
Found = 302,
|
||||
SeeOther = 303,
|
||||
Temporary = 307
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public static class RewriteTags
|
||||
{
|
||||
// TODO More strings to be added later once further implementations are added.
|
||||
public const string Rewrite = "rewrite";
|
||||
public const string GlobalRules = "globalRules";
|
||||
public const string Rules = "rules";
|
||||
public const string Rule = "rule";
|
||||
public const string Action = "action";
|
||||
public const string Name = "name";
|
||||
public const string Enabled = "enabled";
|
||||
public const string PatternSyntax = "patternSyntax";
|
||||
public const string StopProcessing = "stopProcessing";
|
||||
public const string Match = "match";
|
||||
public const string Conditions = "conditions";
|
||||
public const string IgnoreCase = "ignoreCase";
|
||||
public const string Negate = "negate";
|
||||
public const string Url = "url";
|
||||
public const string MatchType = "matchType";
|
||||
public const string Add = "add";
|
||||
public const string TrackingAllCaptures = "trackingAllCaptures";
|
||||
public const string MatchPattern = "matchPattern";
|
||||
public const string Input = "input";
|
||||
public const string Pattern = "pattern";
|
||||
public const string Type = "type";
|
||||
public const string AppendQuery = "appendQuery";
|
||||
public const string LogRewrittenUrl = "logRewrittenUrl";
|
||||
public const string RedirectType = "redirectType";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 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;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.UrlRewrite.PatternSegments;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public static class ServerVariables
|
||||
{
|
||||
public static PatternSegment FindServerVariable(string serverVariable)
|
||||
{
|
||||
switch(serverVariable)
|
||||
{
|
||||
// TODO Add all server variables here.
|
||||
case "ALL_RAW":
|
||||
throw new NotImplementedException();
|
||||
case "APP_POOL_ID":
|
||||
throw new NotImplementedException();
|
||||
case "CONTENT_LENGTH":
|
||||
return new HeaderSegment(HeaderNames.ContentLength);
|
||||
case "CONTENT_TYPE":
|
||||
return new HeaderSegment(HeaderNames.ContentType);
|
||||
case "HTTP_ACCEPT":
|
||||
return new HeaderSegment(HeaderNames.Accept);
|
||||
case "HTTP_COOKIE":
|
||||
return new HeaderSegment(HeaderNames.Cookie);
|
||||
case "HTTP_HOST":
|
||||
return new HeaderSegment(HeaderNames.Host);
|
||||
case "HTTP_PROXY_CONNECTION":
|
||||
return new HeaderSegment(HeaderNames.ProxyAuthenticate);
|
||||
case "HTTP_REFERER":
|
||||
return new HeaderSegment(HeaderNames.Referer);
|
||||
case "HTTP_USER_AGENT":
|
||||
return new HeaderSegment(HeaderNames.UserAgent);
|
||||
case "HTTP_CONNECTION":
|
||||
return new HeaderSegment(HeaderNames.Connection);
|
||||
case "HTTP_URL":
|
||||
return new UrlSegment();
|
||||
case "HTTPS":
|
||||
return new IsHttpsSegment();
|
||||
case "LOCAL_ADDR":
|
||||
return new LocalAddressSegment();
|
||||
case "QUERY_STRING":
|
||||
return new QueryStringSegment();
|
||||
case "REMOTE_ADDR":
|
||||
return new RemoteAddressSegment();
|
||||
case "REMOTE_HOST":
|
||||
throw new NotImplementedException();
|
||||
case "REMOTE_PORT":
|
||||
return new RemotePortSegment();
|
||||
default:
|
||||
throw new FormatException("Unrecognized server variable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public class UrlAction
|
||||
{
|
||||
public ActionType Type { get; set; }
|
||||
public Pattern Url { get; set; }
|
||||
public bool AppendQueryString { get; set; }
|
||||
public bool LogRewrittenUrl { get; set; }
|
||||
public RedirectType RedirectType { get; set; } = RedirectType.Permanent;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Rewrite.UrlRewrite;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public static class UrlRewriteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports rules from a mod_rewrite file and adds the rules to current rules.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="hostingEnv"></param>
|
||||
/// <param name="filePath">The path to the file containing urlrewrite rules.</param>
|
||||
public static UrlRewriteOptions ImportFromUrlRewrite(this UrlRewriteOptions options, IHostingEnvironment hostingEnv, string filePath)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException("UrlRewriteOptions is null");
|
||||
}
|
||||
|
||||
if (hostingEnv == null)
|
||||
{
|
||||
throw new ArgumentNullException("HostingEnvironment is null");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException(nameof(filePath));
|
||||
}
|
||||
|
||||
var path = Path.Combine(hostingEnv.ContentRootPath, filePath);
|
||||
using (var stream = File.OpenRead(path))
|
||||
{
|
||||
options.Rules.AddRange(UrlRewriteFileParser.Parse(new StreamReader(stream)));
|
||||
};
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
// 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 System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
// TODO rename
|
||||
public static class UrlRewriteFileParser
|
||||
{
|
||||
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(1);
|
||||
public static List<UrlRewriteRule> Parse(TextReader reader)
|
||||
{
|
||||
var temp = XDocument.Load(reader);
|
||||
var xmlRoot = temp.Descendants(RewriteTags.Rewrite);
|
||||
var rules = new List<UrlRewriteRule>();
|
||||
|
||||
if (xmlRoot != null)
|
||||
{
|
||||
// there is a valid rewrite block, go through each rule and process
|
||||
GetGlobalRules(xmlRoot.Descendants(RewriteTags.GlobalRules), rules);
|
||||
GetRules(xmlRoot.Descendants(RewriteTags.Rules), rules);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
private static void GetGlobalRules(IEnumerable<XElement> globalRules, List<UrlRewriteRule> result)
|
||||
{
|
||||
foreach (var rule in globalRules.Elements(RewriteTags.Rule) ?? Enumerable.Empty<XElement>())
|
||||
{
|
||||
var res = new UrlRewriteRule();
|
||||
SetRuleAttributes(rule, res);
|
||||
// TODO handle full url with global rules - may or may not support
|
||||
res.Action = CreateUrlAction(rule.Element(RewriteTags.Action));
|
||||
result.Add(res);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetRules(IEnumerable<XElement> rules, List<UrlRewriteRule> result)
|
||||
{
|
||||
// TODO Better null check?
|
||||
foreach (var rule in rules.Elements(RewriteTags.Rule) ?? Enumerable.Empty<XElement>())
|
||||
{
|
||||
var res = new UrlRewriteRule();
|
||||
SetRuleAttributes(rule, res);
|
||||
res.Action = CreateUrlAction(rule.Element(RewriteTags.Action));
|
||||
result.Add(res);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetRuleAttributes(XElement rule, UrlRewriteRule res)
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
res.Name = rule.Attribute(RewriteTags.Name)?.Value;
|
||||
|
||||
bool enabled;
|
||||
if (bool.TryParse(rule.Attribute(RewriteTags.Enabled)?.Value, out enabled))
|
||||
{
|
||||
res.Enabled = enabled;
|
||||
}
|
||||
|
||||
PatternSyntax patternSyntax;
|
||||
if (Enum.TryParse(rule.Attribute(RewriteTags.PatternSyntax)?.Value, out patternSyntax))
|
||||
{
|
||||
res.PatternSyntax = patternSyntax;
|
||||
}
|
||||
|
||||
bool stopProcessing;
|
||||
if (bool.TryParse(rule.Attribute(RewriteTags.StopProcessing)?.Value, out stopProcessing))
|
||||
{
|
||||
res.StopProcessing = stopProcessing;
|
||||
}
|
||||
|
||||
res.Match = CreateMatch(rule.Element(RewriteTags.Match));
|
||||
res.Conditions = CreateConditions(rule.Element(RewriteTags.Conditions));
|
||||
}
|
||||
|
||||
private static InitialMatch CreateMatch(XElement match)
|
||||
{
|
||||
if (match == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matchRes = new InitialMatch();
|
||||
|
||||
bool parBool;
|
||||
if (bool.TryParse(match.Attribute(RewriteTags.IgnoreCase)?.Value, out parBool))
|
||||
{
|
||||
matchRes.IgnoreCase = parBool;
|
||||
}
|
||||
|
||||
if (bool.TryParse(match.Attribute(RewriteTags.Negate)?.Value, out parBool))
|
||||
{
|
||||
matchRes.Negate = parBool;
|
||||
}
|
||||
|
||||
var parsedInputString = match.Attribute(RewriteTags.Url)?.Value;
|
||||
|
||||
if (matchRes.IgnoreCase)
|
||||
{
|
||||
matchRes.Url = new Regex(parsedInputString, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
matchRes.Url = new Regex(parsedInputString, RegexOptions.Compiled, RegexTimeout);
|
||||
}
|
||||
return matchRes;
|
||||
}
|
||||
|
||||
|
||||
private static Conditions CreateConditions(XElement conditions)
|
||||
{
|
||||
var condRes = new Conditions();
|
||||
if (conditions == null)
|
||||
{
|
||||
return condRes; // TODO make sure no null exception on Conditions
|
||||
}
|
||||
|
||||
LogicalGrouping grouping;
|
||||
if (Enum.TryParse(conditions.Attribute(RewriteTags.MatchType)?.Value, out grouping))
|
||||
{
|
||||
condRes.MatchType = grouping;
|
||||
}
|
||||
|
||||
bool parBool;
|
||||
if (bool.TryParse(conditions.Attribute(RewriteTags.TrackingAllCaptures)?.Value, out parBool))
|
||||
{
|
||||
condRes.TrackingAllCaptures = parBool;
|
||||
}
|
||||
|
||||
foreach (var cond in conditions.Elements(RewriteTags.Add))
|
||||
{
|
||||
condRes.ConditionList.Add(CreateCondition(cond));
|
||||
}
|
||||
return condRes;
|
||||
}
|
||||
|
||||
private static Condition CreateCondition(XElement condition)
|
||||
{
|
||||
if (condition == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var condRes = new Condition();
|
||||
|
||||
bool parBool;
|
||||
if (bool.TryParse(condition.Attribute(RewriteTags.IgnoreCase)?.Value, out parBool))
|
||||
{
|
||||
condRes.IgnoreCase = parBool;
|
||||
}
|
||||
|
||||
if (bool.TryParse(condition.Attribute(RewriteTags.Negate)?.Value, out parBool))
|
||||
{
|
||||
condRes.Negate = parBool;
|
||||
}
|
||||
|
||||
MatchType matchType;
|
||||
if (Enum.TryParse(condition.Attribute(RewriteTags.MatchPattern)?.Value, out matchType))
|
||||
{
|
||||
condRes.MatchType = matchType;
|
||||
}
|
||||
|
||||
var parsedInputString = condition.Attribute(RewriteTags.Input)?.Value;
|
||||
if (parsedInputString != null)
|
||||
{
|
||||
condRes.Input = InputParser.ParseInputString(parsedInputString);
|
||||
}
|
||||
|
||||
parsedInputString = condition.Attribute(RewriteTags.Pattern)?.Value;
|
||||
|
||||
if (condRes.IgnoreCase)
|
||||
{
|
||||
condRes.MatchPattern = new Regex(parsedInputString, RegexOptions.IgnoreCase | RegexOptions.Compiled, RegexTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
condRes.MatchPattern = new Regex(parsedInputString, RegexOptions.Compiled, RegexTimeout);
|
||||
}
|
||||
return condRes;
|
||||
}
|
||||
|
||||
private static UrlAction CreateUrlAction(XElement urlAction)
|
||||
{
|
||||
if (urlAction == null)
|
||||
{
|
||||
throw new FormatException("Action is a required element of a rule.");
|
||||
}
|
||||
var actionRes = new UrlAction();
|
||||
|
||||
ActionType actionType;
|
||||
if (Enum.TryParse(urlAction.Attribute(RewriteTags.Type)?.Value, out actionType))
|
||||
{
|
||||
actionRes.Type = actionType;
|
||||
}
|
||||
|
||||
bool parseBool;
|
||||
if (bool.TryParse(urlAction.Attribute(RewriteTags.AppendQuery)?.Value, out parseBool))
|
||||
{
|
||||
actionRes.AppendQueryString = parseBool;
|
||||
}
|
||||
|
||||
if (bool.TryParse(urlAction.Attribute(RewriteTags.LogRewrittenUrl)?.Value, out parseBool))
|
||||
{
|
||||
actionRes.LogRewrittenUrl = parseBool;
|
||||
}
|
||||
|
||||
RedirectType redirectType;
|
||||
if (Enum.TryParse(urlAction.Attribute(RewriteTags.RedirectType)?.Value, out redirectType))
|
||||
{
|
||||
actionRes.RedirectType = redirectType;
|
||||
}
|
||||
|
||||
actionRes.Url = InputParser.ParseInputString(urlAction.Attribute(RewriteTags.Url)?.Value);
|
||||
return actionRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.UrlRewrite
|
||||
{
|
||||
public class UrlRewriteRule : Rule
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public PatternSyntax PatternSyntax { get; set; }
|
||||
public bool StopProcessing { get; set; }
|
||||
public InitialMatch Match { get; set; }
|
||||
public Conditions Conditions { get; set; }
|
||||
public UrlAction Action { get; set; }
|
||||
|
||||
public override RuleResult ApplyRule(UrlRewriteContext context)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": ""
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
// 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.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Rewrite.UrlRewrite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
|
||||
{
|
||||
public class FileParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void RuleParse_ParseTypicalRule()
|
||||
{
|
||||
// arrange
|
||||
var xml = @"<rewrite>
|
||||
<rules>
|
||||
<rule name=""Rewrite to article.aspx"">
|
||||
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
|
||||
<action type=""Rewrite"" url =""article.aspx?id={R:1}&title={R:2}"" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>";
|
||||
|
||||
var expected = new List<UrlRewriteRule>();
|
||||
expected.Add(CreateTestRule(new List<Condition>(),
|
||||
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
|
||||
name: "Rewrite to article.aspx",
|
||||
actionType: ActionType.Rewrite,
|
||||
pattern: "article.aspx?id={R:1}&title={R:2}"));
|
||||
|
||||
// act
|
||||
var res = UrlRewriteFileParser.Parse(new StringReader(xml));
|
||||
|
||||
// assert
|
||||
AssertUrlRewriteRuleEquality(res, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleParse_ParseSingleRuleWithSingleCondition()
|
||||
{
|
||||
// arrange
|
||||
var xml = @"<rewrite>
|
||||
<rules>
|
||||
<rule name=""Rewrite to article.aspx"">
|
||||
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
|
||||
<conditions>
|
||||
<add input=""{HTTPS}"" pattern=""^OFF$"" />
|
||||
</conditions>
|
||||
<action type=""Rewrite"" url =""article.aspx?id={R:1}&title={R:2}"" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>";
|
||||
|
||||
var condList = new List<Condition>();
|
||||
condList.Add(new Condition
|
||||
{
|
||||
Input = InputParser.ParseInputString("{HTTPS}"),
|
||||
MatchPattern = new Regex("^OFF$")
|
||||
});
|
||||
|
||||
var expected = new List<UrlRewriteRule>();
|
||||
expected.Add(CreateTestRule(condList,
|
||||
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
|
||||
name: "Rewrite to article.aspx",
|
||||
actionType: ActionType.Rewrite,
|
||||
pattern: "article.aspx?id={R:1}&title={R:2}"));
|
||||
|
||||
// act
|
||||
var res = UrlRewriteFileParser.Parse(new StringReader(xml));
|
||||
|
||||
// assert
|
||||
AssertUrlRewriteRuleEquality(expected, res);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleParse_ParseMultipleRules()
|
||||
{
|
||||
// arrange
|
||||
var xml = @"<rewrite>
|
||||
<rules>
|
||||
<rule name=""Rewrite to article.aspx"">
|
||||
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
|
||||
<conditions>
|
||||
<add input=""{HTTPS}"" pattern=""^OFF$"" />
|
||||
</conditions>
|
||||
<action type=""Rewrite"" url =""article.aspx?id={R:1}&title={R:2}"" />
|
||||
</rule>
|
||||
<rule name=""Rewrite to article.aspx"">
|
||||
<match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
|
||||
<conditions>
|
||||
<add input=""{HTTPS}"" pattern=""^OFF$"" />
|
||||
</conditions>
|
||||
<action type=""Redirect"" url =""article.aspx?id={R:1}&title={R:2}"" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>";
|
||||
|
||||
var condList = new List<Condition>();
|
||||
condList.Add(new Condition
|
||||
{
|
||||
Input = InputParser.ParseInputString("{HTTPS}"),
|
||||
MatchPattern = new Regex("^OFF$")
|
||||
});
|
||||
|
||||
var expected = new List<UrlRewriteRule>();
|
||||
expected.Add(CreateTestRule(condList,
|
||||
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
|
||||
name: "Rewrite to article.aspx",
|
||||
actionType: ActionType.Rewrite,
|
||||
pattern: "article.aspx?id={R:1}&title={R:2}"));
|
||||
expected.Add(CreateTestRule(condList,
|
||||
Url: "^article/([0-9]+)/([_0-9a-z-]+)",
|
||||
name: "Rewrite to article.aspx",
|
||||
actionType: ActionType.Redirect,
|
||||
pattern: "article.aspx?id={R:1}&title={R:2}"));
|
||||
|
||||
// act
|
||||
var res = UrlRewriteFileParser.Parse(new StringReader(xml));
|
||||
|
||||
// assert
|
||||
AssertUrlRewriteRuleEquality(expected, res);
|
||||
}
|
||||
|
||||
// Creates a rule with appropriate default values of the url rewrite rule.
|
||||
private UrlRewriteRule CreateTestRule(List<Condition> conditions,
|
||||
LogicalGrouping condGrouping = LogicalGrouping.MatchAll,
|
||||
bool condTracking = false,
|
||||
string name = "",
|
||||
bool enabled = true,
|
||||
PatternSyntax patternSyntax = PatternSyntax.ECMAScript,
|
||||
bool stopProcessing = false,
|
||||
string Url = "",
|
||||
bool ignoreCase = true,
|
||||
bool negate = false,
|
||||
ActionType actionType = ActionType.None,
|
||||
string pattern = "",
|
||||
bool appendQueryString = false,
|
||||
bool rewrittenUrl = false,
|
||||
RedirectType redirectType = RedirectType.Permanent
|
||||
)
|
||||
{
|
||||
return new UrlRewriteRule
|
||||
{
|
||||
Action = new UrlAction
|
||||
{
|
||||
Url = InputParser.ParseInputString(pattern),
|
||||
Type = actionType,
|
||||
AppendQueryString = appendQueryString,
|
||||
LogRewrittenUrl = rewrittenUrl,
|
||||
RedirectType = redirectType
|
||||
},
|
||||
Name = name,
|
||||
Enabled = enabled,
|
||||
StopProcessing = stopProcessing,
|
||||
PatternSyntax = patternSyntax,
|
||||
Match = new InitialMatch
|
||||
{
|
||||
Url = new Regex(Url),
|
||||
IgnoreCase = ignoreCase,
|
||||
Negate = negate
|
||||
},
|
||||
Conditions = new Conditions
|
||||
{
|
||||
ConditionList = conditions,
|
||||
MatchType = condGrouping,
|
||||
TrackingAllCaptures = condTracking
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void AssertUrlRewriteRuleEquality(List<UrlRewriteRule> expected, List<UrlRewriteRule> actual)
|
||||
{
|
||||
Assert.Equal(expected.Count, actual.Count);
|
||||
for (var i = 0; i < expected.Count; i++)
|
||||
{
|
||||
var r1 = expected[i];
|
||||
var r2 = actual[i];
|
||||
|
||||
Assert.Equal(r1.Name, r2.Name);
|
||||
Assert.Equal(r1.Enabled, r2.Enabled);
|
||||
Assert.Equal(r1.StopProcessing, r2.StopProcessing);
|
||||
Assert.Equal(r1.PatternSyntax, r2.PatternSyntax);
|
||||
|
||||
Assert.Equal(r1.Match.IgnoreCase, r2.Match.IgnoreCase);
|
||||
Assert.Equal(r1.Match.Negate, r2.Match.Negate);
|
||||
|
||||
Assert.Equal(r1.Action.Type, r2.Action.Type);
|
||||
Assert.Equal(r1.Action.AppendQueryString, r2.Action.AppendQueryString);
|
||||
Assert.Equal(r1.Action.RedirectType, r2.Action.RedirectType);
|
||||
Assert.Equal(r1.Action.LogRewrittenUrl, r2.Action.LogRewrittenUrl);
|
||||
|
||||
// TODO conditions, url pattern, initial match regex
|
||||
Assert.Equal(r1.Conditions.MatchType, r2.Conditions.MatchType);
|
||||
Assert.Equal(r1.Conditions.TrackingAllCaptures, r2.Conditions.TrackingAllCaptures);
|
||||
Assert.Equal(r1.Conditions.ConditionList.Count, r2.Conditions.ConditionList.Count);
|
||||
|
||||
for (var j = 0; j < r1.Conditions.ConditionList.Count; j++)
|
||||
{
|
||||
var c1 = r1.Conditions.ConditionList[j];
|
||||
var c2 = r2.Conditions.ConditionList[j];
|
||||
Assert.Equal(c1.IgnoreCase, c2.IgnoreCase);
|
||||
Assert.Equal(c1.Negate, c2.Negate);
|
||||
Assert.Equal(c1.MatchType, c2.MatchType);
|
||||
Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// 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 System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.UrlRewrite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
|
||||
{
|
||||
public class InputParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void InputParser_ParseLiteralString()
|
||||
{
|
||||
var testString = "hello/hey/what";
|
||||
var result = InputParser.ParseInputString(testString);
|
||||
Assert.Equal(result.PatternSegments.Count, 1);
|
||||
}
|
||||
|
||||
// Tests sizes of the pattern segments. These are all anonyomus lambdas, so cant check contents.
|
||||
[Theory]
|
||||
[InlineData("foo/bar/{R:1}/what", 3)]
|
||||
[InlineData("foo/{R:1}", 2)]
|
||||
[InlineData("foo/{R:1}/{C:2}", 4)]
|
||||
[InlineData("foo/{R:1}{C:2}", 3)]
|
||||
[InlineData("foo/", 1)]
|
||||
public void InputParser_ParseStringWithBackReference(string testString, int expected)
|
||||
{
|
||||
var result = InputParser.ParseInputString(testString);
|
||||
Assert.Equal(result.PatternSegments.Count, expected);
|
||||
}
|
||||
|
||||
// Test actual evaluation of the lambdas, verifying the correct string comes from the evalation
|
||||
[Theory]
|
||||
[InlineData("hey/hello/what", "hey/hello/what")]
|
||||
[InlineData("hey/{R:1}/what", "hey/foo/what")]
|
||||
[InlineData("hey/{R:2}/what", "hey/bar/what")]
|
||||
[InlineData("hey/{R:3}/what", "hey/baz/what")]
|
||||
[InlineData("hey/{C:1}/what", "hey/foo/what")]
|
||||
[InlineData("hey/{C:2}/what", "hey/bar/what")]
|
||||
[InlineData("hey/{C:3}/what", "hey/baz/what")]
|
||||
[InlineData("hey/{R:1}/{C:1}", "hey/foo/foo")]
|
||||
public void EvaluateBackReferenceRule(string testString, string expected)
|
||||
{
|
||||
var middle = InputParser.ParseInputString(testString);
|
||||
var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch());
|
||||
Assert.Equal(result, expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hey/{ToLower:HEY}", "hey/hey")]
|
||||
[InlineData("hey/{ToLower:{R:1}}", "hey/foo")]
|
||||
[InlineData("hey/{ToLower:{C:1}}", "hey/foo")]
|
||||
[InlineData("hey/{ToLower:{C:1}/what}", "hey/foo/what")]
|
||||
[InlineData("hey/ToLower:/what", "hey/ToLower:/what")]
|
||||
public void EvaluatToLowerRule(string testString, string expected)
|
||||
{
|
||||
var middle = InputParser.ParseInputString(testString);
|
||||
var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch());
|
||||
Assert.Equal(result, expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hey/{UrlEncode:<hey>}", "hey/%3Chey%3E")]
|
||||
public void EvaluatUriEncodeRule(string testString, string expected)
|
||||
{
|
||||
var middle = InputParser.ParseInputString(testString);
|
||||
var result = middle.Evaluate(CreateTestHttpContext(), CreateTestRuleMatch(), CreateTestCondMatch());
|
||||
Assert.Equal(result, expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{")]
|
||||
[InlineData("{:}")]
|
||||
[InlineData("{R:")]
|
||||
[InlineData("{R:1")]
|
||||
[InlineData("{R:A}")]
|
||||
[InlineData("{R:10}")]
|
||||
[InlineData("{R:-1}")]
|
||||
[InlineData("{foo:1")]
|
||||
[InlineData("{UrlEncode:{R:}}")]
|
||||
[InlineData("{UrlEncode:{R:1}")]
|
||||
public void FormatExceptionsOnBadSyntax(string testString)
|
||||
{
|
||||
Assert.Throws<FormatException>(() => InputParser.ParseInputString(testString));
|
||||
}
|
||||
|
||||
private HttpContext CreateTestHttpContext()
|
||||
{
|
||||
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
// TODO add fields if necessary
|
||||
return context;
|
||||
}
|
||||
|
||||
private Match CreateTestRuleMatch()
|
||||
{
|
||||
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
|
||||
return match;
|
||||
}
|
||||
|
||||
private Match CreateTestCondMatch()
|
||||
{
|
||||
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue