Url Rewrite WIP
This commit is contained in:
parent
d3816fa458
commit
a9c2656404
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.24720.0
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.xproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
|
||||
EndProject
|
||||
|
|
@ -26,6 +25,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseBufferingSample", "
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HttpOverridesSample", "samples\HttpOverridesSample\HttpOverridesSample.xproj", "{7F95478D-E1D4-4A64-BA42-B041591A96EB}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Rewrite", "src\Microsoft.AspNetCore.Rewrite\Microsoft.AspNetCore.Rewrite.xproj", "{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RewriteSample", "samples\RewriteSample\RewriteSample.xproj", "{9E049645-13BC-4598-89E1-5B43D36E5D14}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Rewrite.Tests", "test\Microsoft.AspNetCore.Rewrite.Tests\Microsoft.AspNetCore.Rewrite.Tests.xproj", "{31794F9E-A1AA-4535-B03C-A3233737CD1A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -56,6 +61,18 @@ Global
|
|||
{7F95478D-E1D4-4A64-BA42-B041591A96EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7F95478D-E1D4-4A64-BA42-B041591A96EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7F95478D-E1D4-4A64-BA42-B041591A96EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9E049645-13BC-4598-89E1-5B43D36E5D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9E049645-13BC-4598-89E1-5B43D36E5D14}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9E049645-13BC-4598-89E1-5B43D36E5D14}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9E049645-13BC-4598-89E1-5B43D36E5D14}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{31794F9E-A1AA-4535-B03C-A3233737CD1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{31794F9E-A1AA-4535-B03C-A3233737CD1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{31794F9E-A1AA-4535-B03C-A3233737CD1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{31794F9E-A1AA-4535-B03C-A3233737CD1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -67,5 +84,8 @@ Global
|
|||
{F5F1D123-9C81-4A9E-8644-AA46B8E578FB} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||
{E5C55B80-7827-40EB-B661-32B0E0E431CA} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||
{7F95478D-E1D4-4A64-BA42-B041591A96EB} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||
{0E7CA1A7-1DC3-4CE6-B9C7-1688FE1410F1} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
|
||||
{9E049645-13BC-4598-89E1-5B43D36E5D14} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
|
||||
{31794F9E-A1AA-4535-B03C-A3233737CD1A} = {8437B0F3-3894-4828-A945-A9187F37631D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RewriteSample")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9e049645-13bc-4598-89e1-5b43d36e5d14")]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# 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]
|
||||
|
||||
# Rewrite path with additional sub directory
|
||||
RewriteRule ^(.*)$ /foo$1
|
||||
|
||||
# Forbid a certain url from being accessed
|
||||
RewriteRule /bar - [F]
|
||||
RewriteRule /bar/ - [F]
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>9e049645-13bc-4598-89e1-5b43d36e5d14</ProjectGuid>
|
||||
<RootNamespace>RewriteSample</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Rewrite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RewriteSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRewriter(new UrlRewriteOptions()
|
||||
.ImportFromModRewrite("Rewrite.txt"));
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"version": "1.1.0-*",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Rewrite": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.StaticFiles": "1.1.0-*"
|
||||
},
|
||||
"buildOptions": {
|
||||
"emitEntryPoint": true,
|
||||
"preserveCompilationContext": true
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {},
|
||||
"netcoreapp1.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0-*",
|
||||
"type": "platform"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"**.xproj",
|
||||
"**.user",
|
||||
"**.vspscc"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>0e7ca1a7-1dc3-4ce6-b9c7-1688fe1410f1</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNetCore.Rewrite</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// 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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// 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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.Operands;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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(UrlRewriteContext context, Match previous, string testString)
|
||||
{
|
||||
return Operand.CheckOperation(previous, testString, context.FileProvider) ^ Invert;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.ModRewrite
|
||||
{
|
||||
public enum ConditionFlagType
|
||||
{
|
||||
NoCase,
|
||||
Or,
|
||||
NoVary
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// 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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Rewrite.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the "CondPattern" portion of the RewriteCond.
|
||||
/// RewriteCond TestString CondPattern
|
||||
/// </summary>
|
||||
public static class ConditionPatternParser
|
||||
{
|
||||
private const char Not = '!';
|
||||
private const char Dash = '-';
|
||||
private const char Less = '<';
|
||||
private const char Greater = '>';
|
||||
private const char EqualSign = '=';
|
||||
|
||||
/// <summary>
|
||||
/// Given a CondPattern, create a ParsedConditionExpression, containing the type of operation
|
||||
/// and value.
|
||||
/// ParsedConditionExpression is an intermediary object, which will be made into a ConditionExpression
|
||||
/// once the flags are parsed.
|
||||
/// </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)
|
||||
{
|
||||
if (condition == null)
|
||||
{
|
||||
condition = string.Empty;
|
||||
}
|
||||
var context = new ParserContext(condition);
|
||||
var results = new ParsedModRewriteExpression();
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
|
||||
// If we hit a !, make sure the condition is inverted when resolving the string
|
||||
if (context.Current == Not)
|
||||
{
|
||||
results.Invert = true;
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
}
|
||||
|
||||
// Control Block for strings. Set the operation and type fields based on the sign
|
||||
switch (context.Current)
|
||||
{
|
||||
case Greater:
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling ">"
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
if (context.Current == EqualSign)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling ">="
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
results.Operation = OperationType.GreaterEqual;
|
||||
results.Type = ConditionType.StringComp;
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Operation = OperationType.Greater;
|
||||
results.Type = ConditionType.StringComp;
|
||||
}
|
||||
break;
|
||||
case Less:
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling "<"
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
if (context.Current == EqualSign)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling "<="
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
results.Operation = OperationType.LessEqual;
|
||||
results.Type = ConditionType.StringComp;
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Operation = OperationType.Less;
|
||||
results.Type = ConditionType.StringComp;
|
||||
}
|
||||
break;
|
||||
case EqualSign:
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling "="
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
results.Operation = OperationType.Equal;
|
||||
results.Type = ConditionType.StringComp;
|
||||
break;
|
||||
case Dash:
|
||||
results = ParseProperty(context, results.Invert);
|
||||
if (results.Type == ConditionType.PropertyTest)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
context.Next();
|
||||
break;
|
||||
default:
|
||||
results.Type = ConditionType.Regex;
|
||||
break;
|
||||
}
|
||||
|
||||
// Capture the rest of the string guarantee validity.
|
||||
results.Operand = (condition.Substring(context.GetIndex()));
|
||||
if (IsValidActionCondition(results))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given that the current index is a property (ex checks for directory or regular files), create a
|
||||
/// new ParsedConditionExpression with the appropriate property operation.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="invert"></param>
|
||||
/// <returns></returns>
|
||||
public static ParsedModRewriteExpression ParseProperty(ParserContext context, bool invert)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
switch (context.Current)
|
||||
{
|
||||
case 'd':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Directory, null);
|
||||
case 'f':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.RegularFile, null);
|
||||
case 'F':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingFile, null);
|
||||
case 'h':
|
||||
case 'L':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null);
|
||||
case 's':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Size, null);
|
||||
case 'U':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.ExistingUrl, null);
|
||||
case 'x':
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.Executable, null);
|
||||
case 'e':
|
||||
|
||||
if (!context.Next() || context.Current != 'q')
|
||||
{
|
||||
// Illegal statement.
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Equal, null);
|
||||
case 'g':
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
if (context.Current == 't')
|
||||
{
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Greater, null);
|
||||
}
|
||||
else if (context.Current == 'e')
|
||||
{
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.GreaterEqual, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
case 'l':
|
||||
if (!context.Next())
|
||||
{
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.PropertyTest, OperationType.SymbolicLink, null);
|
||||
}
|
||||
if (context.Current == 't')
|
||||
{
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.Less, null);
|
||||
}
|
||||
else if (context.Current == 'e')
|
||||
{
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.LessEqual, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
case 'n':
|
||||
if (!context.Next() || context.Current != 'e')
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
return new ParsedModRewriteExpression(invert, ConditionType.IntComp, OperationType.NotEqual, null);
|
||||
default:
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidActionCondition(ParsedModRewriteExpression results)
|
||||
{
|
||||
if (results.Type == ConditionType.IntComp)
|
||||
{
|
||||
// If the type is an integer, verify operand is actually an int
|
||||
int res;
|
||||
if (!int.TryParse(results.Operand, out res))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Rewrite.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the TestString segment of the mod_rewrite condition.
|
||||
/// </summary>
|
||||
public class ConditionTestStringParser
|
||||
{
|
||||
private const char Percent = '%';
|
||||
private const char Dollar = '$';
|
||||
private const char Space = ' ';
|
||||
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 pattern. Can contain server variables, back references, etc.
|
||||
/// </summary>
|
||||
/// <param name="testString">The test string portion of the RewriteCond
|
||||
/// Examples:
|
||||
/// %{REMOTE_ADDR}
|
||||
/// /var/www/%{REQUEST_URI}
|
||||
/// %1
|
||||
/// $1</param>
|
||||
/// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns>
|
||||
public static Pattern ParseConditionTestString(string testString)
|
||||
{
|
||||
if (testString == null)
|
||||
{
|
||||
testString = string.Empty;
|
||||
}
|
||||
var context = new ParserContext(testString);
|
||||
var results = new List<PatternSegment>();
|
||||
while (context.Next())
|
||||
{
|
||||
if (context.Current == Percent)
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
else if (context.Current == Dollar)
|
||||
{
|
||||
// This is a parameter from the rule, verify that it is a number from 0 to 9 directly after it
|
||||
// and create a new Pattern Segment.
|
||||
if (!context.Next())
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
context.Mark();
|
||||
if (context.Current >= '0' && context.Current <= '9')
|
||||
{
|
||||
context.Next();
|
||||
var ruleVariable = context.Capture();
|
||||
context.Back();
|
||||
results.Add(new PatternSegment(ruleVariable, SegmentType.RuleParameter));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(context.Error());
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Pattern(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the condition parameter, which could either be a condition variable or a
|
||||
/// server variable. Assumes the current character is immediately after the '%'.
|
||||
/// context, on return will be on the last character of variable captured, such that after
|
||||
/// Next() is called, it will be on the character immediately after the condition parameter.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
// Parse { }
|
||||
if (context.Current == OpenBrace)
|
||||
{
|
||||
// Start of a server variable
|
||||
if (!context.Next())
|
||||
{
|
||||
// Dangling {
|
||||
return false;
|
||||
}
|
||||
context.Mark();
|
||||
while (context.Current != CloseBrace)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// No closing } for the server variable
|
||||
return false;
|
||||
}
|
||||
else if (context.Current == Colon)
|
||||
{
|
||||
// Have a segmented look up Ex: HTTP:xxxx
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else if (context.Current >= '0' && context.Current <= '9')
|
||||
{
|
||||
// means we have a segmented lookup
|
||||
// store information in the testString result to know what to look up.
|
||||
context.Mark();
|
||||
context.Next();
|
||||
var rawConditionParameter = context.Capture();
|
||||
// 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));
|
||||
}
|
||||
else
|
||||
{
|
||||
// illegal escape of a character
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a string literal in the test string. Continues capturing until the start of a new variable type.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="results"></param>
|
||||
/// <returns></returns>
|
||||
private static bool ParseLiteral(ParserContext context, List<PatternSegment> results)
|
||||
{
|
||||
context.Mark();
|
||||
string literal;
|
||||
while (true)
|
||||
{
|
||||
if (context.Current == Percent || context.Current == Dollar)
|
||||
{
|
||||
literal = context.Capture();
|
||||
context.Back();
|
||||
break;
|
||||
}
|
||||
if (!context.Next())
|
||||
{
|
||||
literal = context.Capture();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.ModRewrite
|
||||
{
|
||||
public enum ConditionType
|
||||
{
|
||||
Regex,
|
||||
PropertyTest,
|
||||
StringComp,
|
||||
IntComp
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// 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.Operands;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// 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 Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class FileParser
|
||||
{
|
||||
public static 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
|
||||
while ((line = input.ReadLine()) != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var tokens = Tokenizer.Tokenize(line);
|
||||
if (tokens.Count > 4)
|
||||
{
|
||||
// This means the line didn't have an appropriate format, throw format exception
|
||||
throw new FormatException();
|
||||
}
|
||||
// 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;
|
||||
case "RewriteCond":
|
||||
{
|
||||
ConditionBuilder builder = null;
|
||||
if (tokens.Count == 3)
|
||||
{
|
||||
builder = new ConditionBuilder(tokens[1], tokens[2]);
|
||||
}
|
||||
else if (tokens.Count == 4)
|
||||
{
|
||||
builder = new ConditionBuilder(tokens[1], tokens[2], tokens[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
conditions.Add(builder.Build());
|
||||
break;
|
||||
}
|
||||
case "RewriteRule":
|
||||
{
|
||||
RuleBuilder builder = null;
|
||||
if (tokens.Count == 3)
|
||||
{
|
||||
builder = new RuleBuilder(tokens[1], tokens[2]);
|
||||
}
|
||||
else if (tokens.Count == 4)
|
||||
{
|
||||
builder = new RuleBuilder(tokens[1], tokens[2], tokens[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
builder.AddConditions(conditions);
|
||||
rules.Add(builder.Build());
|
||||
conditions = new List<Condition>();
|
||||
break;
|
||||
}
|
||||
case "RewriteMap":
|
||||
throw new NotImplementedException("RewriteMaps to be added soon.");
|
||||
case "RewriteEngine":
|
||||
// Explicitly do nothing here, no notion of turning on regex engine.
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(tokens[0]);
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
// 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.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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(flagString))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 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.
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.ModRewrite;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public static class ModRewriteExtensions
|
||||
{
|
||||
/// <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="filePath">The path to the file containing mod_rewrite rules.</param>
|
||||
public static UrlRewriteOptions ImportFromModRewrite(this UrlRewriteOptions options, string filePath)
|
||||
{
|
||||
// TODO use IHostingEnvironment as param.
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException(nameof(filePath));
|
||||
}
|
||||
// TODO IHosting to fix!
|
||||
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
options.Rules.AddRange(FileParser.Parse(new StreamReader(stream)));
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <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="reader">Text reader containing a stream of mod_rewrite rules.</param>
|
||||
public static UrlRewriteOptions ImportFromModRewrite(this UrlRewriteOptions options, TextReader reader)
|
||||
{
|
||||
options.Rules.AddRange(FileParser.Parse(reader));
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a mod_rewrite rule to the current rules.
|
||||
/// Additional properties (conditions, flags) for the rule can be added through the action.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="rule">The literal string of a mod_rewrite rule:
|
||||
/// "RewriteRule Pattern Substitution [Flags]"</param>
|
||||
/// <param name="action">Action to perform on the <see cref="RuleBuilder"/> </param>
|
||||
public static UrlRewriteOptions AddModRewriteRule(this UrlRewriteOptions options, string rule, Action<RuleBuilder> action)
|
||||
{
|
||||
var builder = new RuleBuilder(rule);
|
||||
action(builder);
|
||||
options.Rules.Add(builder.Build());
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a mod_rewrite rule to the current rules.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="rule">The literal string of a mod_rewrite rule:
|
||||
/// "RewriteRule Pattern Substitution [Flags]"</param>
|
||||
public static UrlRewriteOptions AddModRewriteRule(this UrlRewriteOptions options, string rule)
|
||||
{
|
||||
var builder = new RuleBuilder(rule);
|
||||
options.Rules.Add(builder.Build());
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
// 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.RuleAbstraction;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
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 ModRewriteRule(List<Condition> conditions, RuleExpression initialRule, Pattern transforms, RuleFlags flags, string description = "")
|
||||
{
|
||||
Conditions = conditions;
|
||||
InitialRule = initialRule;
|
||||
Transform = transforms;
|
||||
Flags = flags;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public override RuleResult ApplyRule(UrlRewriteContext 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());
|
||||
|
||||
string flagRes = null;
|
||||
if (CheckMatchResult(results.Success))
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.Continue };
|
||||
}
|
||||
|
||||
if (Flags.HasFlag(RuleFlagType.EscapeBackreference))
|
||||
{
|
||||
// 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 new RuleResult { Result = RuleTerminiation.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 new RuleResult { Result = RuleTerminiation.ResponseComplete };
|
||||
}
|
||||
else if (Flags.HasFlag(RuleFlagType.Gone))
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status410Gone;
|
||||
return new RuleResult { Result = RuleTerminiation.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 new RuleResult { Result = RuleTerminiation.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 new RuleResult { Result = RuleTerminiation.StopRules };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.Continue };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckMatchResult(bool? result)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return !(result.Value ^ InitialRule.Invert);
|
||||
}
|
||||
|
||||
private bool CheckCondition(UrlRewriteContext context, Match results, Match previous)
|
||||
{
|
||||
if (Conditions == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
None,
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
NotEqual,
|
||||
Directory,
|
||||
RegularFile,
|
||||
ExistingFile,
|
||||
SymbolicLink,
|
||||
Size,
|
||||
ExistingUrl,
|
||||
Executable
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
public class ParsedModRewriteExpression
|
||||
{
|
||||
public bool Invert { get; set; }
|
||||
public ConditionType Type { get; set; }
|
||||
public OperationType Operation { get; set; }
|
||||
public string Operand { get; set; }
|
||||
public ParsedModRewriteExpression(bool invert, ConditionType type, OperationType operation, string operand)
|
||||
{
|
||||
Invert = invert;
|
||||
Type = type;
|
||||
Operation = operation;
|
||||
Operand = operand;
|
||||
}
|
||||
|
||||
public ParsedModRewriteExpression() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
// 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.ModRewrite
|
||||
{
|
||||
public class RuleBuilder
|
||||
{
|
||||
private ParsedModRewriteExpression _pce;
|
||||
private List<Condition> _conditions;
|
||||
private RuleFlags _flags;
|
||||
private Pattern _patterns;
|
||||
public ModRewriteRule Build()
|
||||
{
|
||||
var ruleExpression = ExpressionCreator.CreateRuleExpression(_pce, _flags);
|
||||
return new ModRewriteRule(_conditions, ruleExpression, _patterns, _flags);
|
||||
}
|
||||
|
||||
public RuleBuilder(string initialRule, string transformation) :
|
||||
this(initialRule, transformation, flags: null)
|
||||
{
|
||||
}
|
||||
public RuleBuilder(string rule)
|
||||
{
|
||||
var tokens = Tokenizer.Tokenize(rule);
|
||||
if (tokens.Count == 3)
|
||||
{
|
||||
CreateRule(tokens[1], tokens[2], flags: null);
|
||||
}
|
||||
else if (tokens.Count == 4)
|
||||
{
|
||||
CreateRule(tokens[1], tokens[2], tokens[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public RuleBuilder(string initialRule, string transformation, string 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)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
else
|
||||
{
|
||||
FlagParser.ParseRuleFlags(flags, _flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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.ModRewrite
|
||||
{
|
||||
public enum RuleFlagType
|
||||
{
|
||||
EscapeBackreference,
|
||||
Chain,
|
||||
Cookie,
|
||||
DiscardPath,
|
||||
Env,
|
||||
End,
|
||||
Forbidden,
|
||||
Gone,
|
||||
Handler,
|
||||
Last,
|
||||
Next,
|
||||
NoCase,
|
||||
NoEscape,
|
||||
NoSubReq,
|
||||
NoVary,
|
||||
Or,
|
||||
Proxy,
|
||||
PassThrough,
|
||||
QSAppend,
|
||||
QSDiscard,
|
||||
QSLast,
|
||||
Redirect,
|
||||
Skip,
|
||||
Type,
|
||||
// Non-modrewrite rule
|
||||
FullUrl
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
// 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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
public static class RuleRegexParser
|
||||
{
|
||||
public static ParsedModRewriteExpression ParseRuleRegex(string regex)
|
||||
{
|
||||
if (regex == null || regex == String.Empty)
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
if (regex.StartsWith("!"))
|
||||
{
|
||||
return new ParsedModRewriteExpression { Invert = true, Operand = regex.Substring(1) };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ParsedModRewriteExpression { Invert = false, Operand = regex};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
public enum SegmentType
|
||||
{
|
||||
Literal,
|
||||
ServerParameter,
|
||||
ConditionParameter,
|
||||
RuleParameter
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
// 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.Globalization;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// mod_rewrite lookups for specific string constants.
|
||||
/// </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>
|
||||
/// <returns>The appropriate enum if the server variable exists, else ServerVariable.None</returns>
|
||||
public static string Resolve(string variable, HttpContext context)
|
||||
{
|
||||
// TODO talk about perf here
|
||||
switch (variable)
|
||||
{
|
||||
case "HTTP_ACCEPT":
|
||||
return context.Request.Headers[HeaderNames.Accept];
|
||||
case "HTTP_COOKIE":
|
||||
return context.Request.Headers[HeaderNames.Cookie];
|
||||
case "HTTP_FORWARDED":
|
||||
return context.Request.Headers["Forwarded"];
|
||||
case "HTTP_HOST":
|
||||
return context.Request.Headers[HeaderNames.Host];
|
||||
case "HTTP_PROXY_CONNECTION":
|
||||
return context.Request.Headers[HeaderNames.ProxyAuthenticate];
|
||||
case "HTTP_REFERER":
|
||||
return context.Request.Headers[HeaderNames.Referer];
|
||||
case "HTTP_USER_AGENT":
|
||||
return context.Request.Headers[HeaderNames.UserAgent];
|
||||
case "AUTH_TYPE":
|
||||
throw new NotImplementedException();
|
||||
case "CONN_REMOTE_ADDR":
|
||||
return context.Connection.RemoteIpAddress?.ToString();
|
||||
case "CONTEXT_PREFIX":
|
||||
throw new NotImplementedException();
|
||||
case "CONTEXT_DOCUMENT_ROOT":
|
||||
throw new NotImplementedException();
|
||||
case "IPV6":
|
||||
return context.Connection.LocalIpAddress.AddressFamily == AddressFamily.InterNetworkV6 ? "on" : "off";
|
||||
case "PATH_INFO":
|
||||
throw new NotImplementedException();
|
||||
case "QUERY_STRING":
|
||||
return context.Request.QueryString.Value;
|
||||
case "REMOTE_ADDR":
|
||||
return context.Connection.RemoteIpAddress?.ToString();
|
||||
case "REMOTE_HOST":
|
||||
throw new NotImplementedException();
|
||||
case "REMOTE_IDENT":
|
||||
throw new NotImplementedException();
|
||||
case "REMOTE_PORT":
|
||||
return context.Connection.RemotePort.ToString(CultureInfo.InvariantCulture);
|
||||
case "REMOTE_USER":
|
||||
throw new NotImplementedException();
|
||||
case "REQUEST_METHOD":
|
||||
return context.Request.Method;
|
||||
case "SCRIPT_FILENAME":
|
||||
throw new NotImplementedException();
|
||||
case "DOCUMENT_ROOT":
|
||||
throw new NotImplementedException();
|
||||
case "SCRIPT_GROUP":
|
||||
throw new NotImplementedException();
|
||||
case "SCRIPT_USER":
|
||||
throw new NotImplementedException();
|
||||
case "SERVER_ADDR":
|
||||
return context.Connection.LocalIpAddress?.ToString();
|
||||
case "SERVER_ADMIN":
|
||||
throw new NotImplementedException();
|
||||
case "SERVER_NAME":
|
||||
throw new NotImplementedException();
|
||||
case "SERVER_PORT":
|
||||
return context.Connection.LocalPort.ToString(CultureInfo.InvariantCulture);
|
||||
case "SERVER_PROTOCOL":
|
||||
return context.Features.Get<IHttpRequestFeature>()?.Protocol;
|
||||
case "SERVER_SOFTWARE":
|
||||
throw new NotImplementedException();
|
||||
case "TIME_YEAR":
|
||||
return DateTimeOffset.UtcNow.Year.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_MON":
|
||||
return DateTimeOffset.UtcNow.Month.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_DAY":
|
||||
return DateTimeOffset.UtcNow.Day.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_HOUR":
|
||||
return DateTimeOffset.UtcNow.Hour.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_MIN":
|
||||
return DateTimeOffset.UtcNow.Minute.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_SEC":
|
||||
return DateTimeOffset.UtcNow.Second.ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME_WDAY":
|
||||
return ((int) DateTimeOffset.UtcNow.DayOfWeek).ToString(CultureInfo.InvariantCulture);
|
||||
case "TIME":
|
||||
return DateTimeOffset.UtcNow.ToString(CultureInfo.InvariantCulture);
|
||||
case "API_VERSION":
|
||||
throw new NotImplementedException();
|
||||
case "HTTPS":
|
||||
return context.Request.IsHttps ? "on" : "off";
|
||||
case "HTTP2":
|
||||
return context.Request.Scheme == "http2" ? "on" : "off";
|
||||
case "IS_SUBREQ":
|
||||
// TODO maybe can do this? context.Request.HttpContext ?
|
||||
throw new NotImplementedException();
|
||||
case "REQUEST_FILENAME":
|
||||
return context.Request.Path.Value.Substring(1);
|
||||
case "REQUEST_SCHEME":
|
||||
return context.Request.Scheme;
|
||||
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);
|
||||
case "THE_REQUEST":
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Rewrite.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.ModRewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Tokenizes a mod_rewrite rule, delimited by spaces.
|
||||
/// </summary>
|
||||
public static class Tokenizer
|
||||
{
|
||||
private const char Space = ' ';
|
||||
private const char Escape = '\\';
|
||||
private const char Tab = '\t';
|
||||
|
||||
/// <summary>
|
||||
/// Splits a string on whitespace, ignoring spaces, creating into a list of strings.
|
||||
/// </summary>
|
||||
/// <param name="rule">The rule to tokenize.</param>
|
||||
/// <returns>A list of tokens.</returns>
|
||||
public static List<string> Tokenize(string rule)
|
||||
{
|
||||
// TODO make list of strings a reference to the original rule? (run into problems with escaped spaces).
|
||||
// TODO handle "s and probably replace \ character with no slash.
|
||||
if (string.IsNullOrEmpty(rule))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var context = new ParserContext(rule);
|
||||
if (!context.Next())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var tokens = new List<string>();
|
||||
context.Mark();
|
||||
while (true)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// End of string. Capture.
|
||||
break;
|
||||
}
|
||||
else if (context.Current == Escape)
|
||||
{
|
||||
// Need to progress such that the next character is not evaluated.
|
||||
if (!context.Next())
|
||||
{
|
||||
// Means that a character was not escaped appropriately Ex: "foo\"
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
else if (context.Current == Space || context.Current == Tab)
|
||||
{
|
||||
// time to capture!
|
||||
// TODO: This kinda sucks, set state and skip
|
||||
var token = context.Capture();
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
tokens.Add(token);
|
||||
while (context.Current == Space || context.Current == Tab)
|
||||
{
|
||||
if (!context.Next())
|
||||
{
|
||||
// At end of string, we can return at this point.
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
context.Mark();
|
||||
}
|
||||
}
|
||||
}
|
||||
var done = context.Capture();
|
||||
if (!string.IsNullOrEmpty(done))
|
||||
{
|
||||
tokens.Add(done);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// 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.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Operands
|
||||
{
|
||||
public class IntegerOperand : Operand
|
||||
{
|
||||
public int Value { get; }
|
||||
public IntegerOperationType Operation { get; }
|
||||
public IntegerOperand(int value, IntegerOperationType operation)
|
||||
{
|
||||
Value = value;
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
public IntegerOperand(string value, IntegerOperationType operation)
|
||||
{
|
||||
int compValue;
|
||||
if (!int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out compValue))
|
||||
{
|
||||
throw new FormatException("Syntax error for integers in comparison.");
|
||||
}
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
public override bool? CheckOperation(Match previous, string testString, IFileProvider fileProvider)
|
||||
{
|
||||
int compValue;
|
||||
if (!int.TryParse(testString, NumberStyles.None, CultureInfo.InvariantCulture, out compValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
switch (Operation)
|
||||
{
|
||||
case IntegerOperationType.Equal:
|
||||
return compValue == Value;
|
||||
case IntegerOperationType.Greater:
|
||||
return compValue > Value;
|
||||
case IntegerOperationType.GreaterEqual:
|
||||
return compValue >= Value;
|
||||
case IntegerOperationType.Less:
|
||||
return compValue < Value;
|
||||
case IntegerOperationType.LessEqual:
|
||||
return compValue <= Value;
|
||||
case IntegerOperationType.NotEqual:
|
||||
return compValue != Value;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Operands
|
||||
{
|
||||
public enum IntegerOperationType
|
||||
{
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual,
|
||||
NotEqual
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Operands
|
||||
{
|
||||
public abstract class Operand
|
||||
{
|
||||
public abstract bool? CheckOperation(Match previous, string concatTestString, IFileProvider fileProvider);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.Operands
|
||||
{
|
||||
public enum PropertyOperationType
|
||||
{
|
||||
None,
|
||||
Directory,
|
||||
RegularFile,
|
||||
ExistingFile,
|
||||
SymbolicLink,
|
||||
Size,
|
||||
ExistingUrl,
|
||||
Executable
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Operands
|
||||
{
|
||||
public enum StringOperationType
|
||||
{
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a string iterator, with captures.
|
||||
/// </summary>
|
||||
public class ParserContext
|
||||
{
|
||||
private readonly string _template;
|
||||
private int _index;
|
||||
private int? _mark;
|
||||
|
||||
public ParserContext(string condition)
|
||||
{
|
||||
_template = condition;
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
public char Current
|
||||
{
|
||||
get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
|
||||
}
|
||||
|
||||
public bool Back()
|
||||
{
|
||||
return --_index >= 0;
|
||||
}
|
||||
|
||||
public bool Next()
|
||||
{
|
||||
return ++_index < _template.Length;
|
||||
}
|
||||
|
||||
public bool HasNext()
|
||||
{
|
||||
return (_index + 1) < _template.Length;
|
||||
}
|
||||
|
||||
public void Mark()
|
||||
{
|
||||
_mark = _index;
|
||||
}
|
||||
|
||||
public int GetIndex()
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
|
||||
public string Capture()
|
||||
{
|
||||
// TODO make this return a range rather than a string.
|
||||
if (_mark.HasValue)
|
||||
{
|
||||
var value = _template.Substring(_mark.Value, _index - _mark.Value);
|
||||
_mark = null;
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public string Error()
|
||||
{
|
||||
return string.Format("Syntax Error at index: ", _index, " with character: ", Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.AspNetCore.Rewrite")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("0e7ca1a7-1dc3-4ce6-b9c7-1688fe1410f1")]
|
||||
|
|
@ -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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.RuleAbstraction
|
||||
{
|
||||
public class FunctionalRule : Rule
|
||||
{
|
||||
public Func<UrlRewriteContext, RuleResult> OnApplyRule { get; set; }
|
||||
public Transformation OnCompletion { get; set; } = Transformation.Rewrite;
|
||||
public override RuleResult ApplyRule(UrlRewriteContext context) => OnApplyRule(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// 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.RuleAbstraction
|
||||
{
|
||||
public class PathRule : Rule
|
||||
{
|
||||
public Regex MatchPattern { get; set; }
|
||||
public string OnMatch { get; set; }
|
||||
public Transformation OnCompletion { get; set; } = Transformation.Rewrite;
|
||||
public override RuleResult ApplyRule(UrlRewriteContext context)
|
||||
{
|
||||
var matches = MatchPattern.Match(context.HttpContext.Request.Path);
|
||||
if (matches.Success)
|
||||
{
|
||||
// New method here to translate the outgoing format string to the correct value.
|
||||
var path = matches.Result(OnMatch);
|
||||
if (OnCompletion == Transformation.Redirect)
|
||||
{
|
||||
var req = context.HttpContext.Request;
|
||||
var newUrl = string.Concat(
|
||||
req.Scheme,
|
||||
"://",
|
||||
req.PathBase,
|
||||
path,
|
||||
req.QueryString);
|
||||
context.HttpContext.Response.Redirect(newUrl);
|
||||
return new RuleResult { Result = RuleTerminiation.ResponseComplete };
|
||||
}
|
||||
else
|
||||
{
|
||||
context.HttpContext.Request.Path = path;
|
||||
}
|
||||
if (OnCompletion == Transformation.TerminatingRewrite)
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.StopRules };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.Continue };
|
||||
}
|
||||
}
|
||||
return new RuleResult { Result = RuleTerminiation.Continue };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.RuleAbstraction
|
||||
{
|
||||
public abstract class Rule
|
||||
{
|
||||
public abstract RuleResult ApplyRule(UrlRewriteContext context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
using Microsoft.AspNetCore.Rewrite.Operands;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.RuleAbstraction
|
||||
{
|
||||
public class RuleExpression
|
||||
{
|
||||
public RegexOperand Operand { get; set; }
|
||||
public bool Invert { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.RuleAbstraction
|
||||
{
|
||||
public class RuleResult
|
||||
{
|
||||
public RuleTerminiation Result { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.RuleAbstraction
|
||||
{
|
||||
public enum RuleTerminiation
|
||||
{
|
||||
Continue,
|
||||
ResponseComplete,
|
||||
StopRules
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +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.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite.RuleAbstraction
|
||||
{
|
||||
public class SchemeRule : Rule
|
||||
{
|
||||
public int? SSLPort { get; set; }
|
||||
public Transformation OnCompletion { get; set; } = Transformation.Rewrite;
|
||||
public override RuleResult ApplyRule(UrlRewriteContext 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;
|
||||
if (SSLPort.HasValue && SSLPort.Value > 0)
|
||||
{
|
||||
// a specific SSL port is specified
|
||||
host = new HostString(host.Host, SSLPort.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// clear the port
|
||||
host = new HostString(host.Host);
|
||||
}
|
||||
|
||||
if ((OnCompletion != Transformation.Redirect))
|
||||
{
|
||||
context.HttpContext.Request.Scheme = "https";
|
||||
context.HttpContext.Request.Host = host;
|
||||
if (OnCompletion == Transformation.TerminatingRewrite)
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.StopRules };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RuleResult { Result = RuleTerminiation.Continue };
|
||||
}
|
||||
}
|
||||
|
||||
var req = context.HttpContext.Request;
|
||||
|
||||
var newUrl = new StringBuilder().Append("https://").Append(host).Append(req.PathBase).Append(req.Path).Append(req.QueryString);
|
||||
context.HttpContext.Response.Redirect(newUrl.ToString());
|
||||
return new RuleResult { Result = RuleTerminiation.ResponseComplete };
|
||||
}
|
||||
return new RuleResult { Result = RuleTerminiation.Continue }; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.RuleAbstraction
|
||||
{
|
||||
public enum Transformation
|
||||
{
|
||||
Rewrite,
|
||||
Redirect,
|
||||
TerminatingRewrite
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// The UrlRewrite Context contains the HttpContext of the request and the file provider to check conditions.
|
||||
/// </summary>
|
||||
public class UrlRewriteContext
|
||||
{
|
||||
public HttpContext HttpContext { get; set; }
|
||||
public IFileProvider FileProvider { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="UrlRewriteMiddleware"/>
|
||||
/// </summary>
|
||||
public static class UrlRewriteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a given Url matches rules and conditions, and modifies the HttpContext on match.
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="options">Options for urlrewrite.</param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseRewriter(this IApplicationBuilder app, UrlRewriteOptions options)
|
||||
{
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
// put middleware in pipeline
|
||||
return app.UseMiddleware<UrlRewriteMiddleware>(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a middleware that rewrites urls imported from mod_rewrite, UrlRewrite, and code.
|
||||
/// </summary>
|
||||
public class UrlRewriteMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly UrlRewriteOptions _options;
|
||||
private readonly IFileProvider _fileProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="UrlRewriteMiddleware"/>
|
||||
/// </summary>
|
||||
/// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
|
||||
/// <param name="hostingEnv">The Hosting Environment.</param>
|
||||
/// <param name="options">The middleware options, containing the rules to apply.</param>
|
||||
public UrlRewriteMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, UrlRewriteOptions options)
|
||||
{
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_options = options;
|
||||
_fileProvider = _options.FileProvider ?? hostingEnv.WebRootFileProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the middleware.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
|
||||
/// <returns>A task that represents the execution of this middleware.</returns>
|
||||
public Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
var urlContext = new UrlRewriteContext { HttpContext = context, FileProvider = _fileProvider };
|
||||
foreach (var rule in _options.Rules)
|
||||
{
|
||||
// Apply the rule
|
||||
var result = rule.ApplyRule(urlContext);
|
||||
switch (result.Result)
|
||||
{
|
||||
case RuleTerminiation.Continue:
|
||||
// Explicitly show that we continue executing rules
|
||||
break;
|
||||
case RuleTerminiation.ResponseComplete:
|
||||
// TODO cache task for perf
|
||||
return Task.FromResult(0);
|
||||
case RuleTerminiation.StopRules:
|
||||
return _next(context);
|
||||
}
|
||||
}
|
||||
return _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the <see cref="UrlRewriteMiddleware"/>
|
||||
/// </summary>
|
||||
public class UrlRewriteOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The ordered list of rules to apply to the context.
|
||||
/// </summary>
|
||||
public List<Rule> Rules { get; set; } = new List<Rule>();
|
||||
public IFileProvider FileProvider { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// 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.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
/// <summary>
|
||||
/// The builder to a list of rules for <see cref="UrlRewriteOptions"/> and <see cref="UrlRewriteMiddleware"/>
|
||||
/// </summary>
|
||||
public static class UrlRewriteOptionsAddRulesExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a rule to the current rules.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="rule">A rule to be added to the current rules.</param>
|
||||
public static UrlRewriteOptions AddRule(this UrlRewriteOptions options, Rule rule)
|
||||
{
|
||||
options.Rules.Add(rule);
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a list of rules to the current rules.
|
||||
/// </summary>
|
||||
/// <param name="options">The UrlRewrite options.</param>
|
||||
/// <param name="rules">A list of rules.</param>
|
||||
public static UrlRewriteOptions AddRules(this UrlRewriteOptions options, List<Rule> rules)
|
||||
{
|
||||
options.Rules.AddRange(rules);
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rewrite path rule.
|
||||
/// </summary>
|
||||
/// <param name="options">The Url rewrite options.</param>
|
||||
/// <param name="regex">The string regex pattern to compare against the http context.</param>
|
||||
/// <param name="newPath">The string to replace the path with (with capture parameters).</param>
|
||||
/// <param name="stopRewriteOnSuccess">Whether or not to stop rewriting on success of rule.</param>
|
||||
/// <returns></returns>
|
||||
public static UrlRewriteOptions RewritePath(this UrlRewriteOptions options, string regex, string newPath, bool stopRewriteOnSuccess = false)
|
||||
{
|
||||
options.Rules.Add(new PathRule { MatchPattern = new Regex(regex, RegexOptions.Compiled, TimeSpan.FromMilliseconds(1)), OnMatch = newPath, OnCompletion = stopRewriteOnSuccess ? Transformation.TerminatingRewrite : Transformation.Rewrite });
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewrite http to https.
|
||||
/// </summary>
|
||||
/// <param name="options">The Url rewrite options.</param>
|
||||
/// <param name="stopRewriteOnSuccess">Whether or not to stop rewriting on success of rule.</param>
|
||||
/// <returns></returns>
|
||||
public static UrlRewriteOptions RewriteScheme(this UrlRewriteOptions options, bool stopRewriteOnSuccess = false)
|
||||
{
|
||||
options.Rules.Add(new SchemeRule {OnCompletion = stopRewriteOnSuccess ? Transformation.TerminatingRewrite : Transformation.Rewrite });
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirect a path to another path.
|
||||
/// </summary>
|
||||
/// <param name="options">The Url rewrite options.</param>
|
||||
/// <param name="regex">The string regex pattern to compare against the http context.</param>
|
||||
/// <param name="newPath">The string to replace the path with (with capture parameters).</param>
|
||||
/// <param name="stopRewriteOnSuccess">Whether or not to stop rewriting on success of rule.</param>
|
||||
/// <returns></returns>
|
||||
public static UrlRewriteOptions RedirectPath(this UrlRewriteOptions options, string regex, string newPath, bool stopRewriteOnSuccess = false)
|
||||
{
|
||||
options.Rules.Add(new PathRule { MatchPattern = new Regex(regex, RegexOptions.Compiled, TimeSpan.FromMilliseconds(1)), OnMatch = newPath, OnCompletion = Transformation.Redirect });
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redirect http to https.
|
||||
/// </summary>
|
||||
/// <param name="options">The Url rewrite options.</param>
|
||||
/// <param name="sslPort">The port to redirect the scheme to.</param>
|
||||
/// <returns></returns>
|
||||
public static UrlRewriteOptions RedirectScheme(this UrlRewriteOptions options, int? sslPort)
|
||||
{
|
||||
options.Rules.Add(new SchemeRule { SSLPort = sslPort, OnCompletion = Transformation.Redirect });
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User generated rule to do a specific match on a path and what to do on success of the match.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="onApplyRule"></param>
|
||||
/// <param name="transform"></param>
|
||||
/// <param name="description"></param>
|
||||
/// <returns></returns>
|
||||
public static UrlRewriteOptions CustomRule(this UrlRewriteOptions options, Func<UrlRewriteContext, RuleResult> onApplyRule, Transformation transform, string description = null)
|
||||
{
|
||||
options.Rules.Add(new FunctionalRule { OnApplyRule = onApplyRule, OnCompletion = transform});
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"version": "1.1.0-*",
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true,
|
||||
"keyFile": "../../tools/Key.snk",
|
||||
"nowarn": [
|
||||
"CS1591"
|
||||
],
|
||||
"xmlDoc": true
|
||||
},
|
||||
"description": "ASP.NET Core basic middleware for:\r\nRewrite Url module.",
|
||||
"packOptions": {
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/aspnet/basicmiddleware"
|
||||
},
|
||||
"tags": [
|
||||
"aspnetcore",
|
||||
"proxy",
|
||||
"headers",
|
||||
"xforwarded"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.Http.Extensions": "1.1.0-*",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "1.1.0-*",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "1.1.0-*",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "1.1.0-*",
|
||||
"Microsoft.Extensions.Options": "1.1.0-*",
|
||||
"System.Text.RegularExpressions": "4.1.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {},
|
||||
"netstandard1.3": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
// 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.ModRewrite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class ConditionActionTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(">hey", OperationType.Greater, "hey", ConditionType.StringComp)]
|
||||
[InlineData("<hey", OperationType.Less, "hey", ConditionType.StringComp)]
|
||||
[InlineData(">=hey", OperationType.GreaterEqual, "hey", ConditionType.StringComp)]
|
||||
[InlineData("<=hey", OperationType.LessEqual, "hey", ConditionType.StringComp)]
|
||||
[InlineData("=hey", OperationType.Equal, "hey", ConditionType.StringComp)]
|
||||
public void ConditionParser_CheckStringComp(string condition, OperationType operation, string variable, ConditionType conditionType)
|
||||
{
|
||||
var results = ConditionPatternParser.ParseActionCondition(condition);
|
||||
|
||||
var expected = new ParsedModRewriteExpression { Operation = operation, Type = conditionType, Operand = variable, Invert = false };
|
||||
Assert.True(CompareConditions(results, expected));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionParser_CheckRegexEqual()
|
||||
{
|
||||
var condition = @"(.*)";
|
||||
var results = ConditionPatternParser.ParseActionCondition(condition);
|
||||
|
||||
var expected = new ParsedModRewriteExpression { Type = ConditionType.Regex, Operand = "(.*)", Invert = false };
|
||||
Assert.True(CompareConditions(results, expected));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-d", OperationType.Directory, ConditionType.PropertyTest)]
|
||||
[InlineData("-f", OperationType.RegularFile, ConditionType.PropertyTest)]
|
||||
[InlineData("-F", OperationType.ExistingFile, ConditionType.PropertyTest)]
|
||||
[InlineData("-h", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("-L", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("-l", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("-s", OperationType.Size, ConditionType.PropertyTest)]
|
||||
[InlineData("-U", OperationType.ExistingUrl, ConditionType.PropertyTest)]
|
||||
[InlineData("-x", OperationType.Executable, ConditionType.PropertyTest)]
|
||||
public void ConditionParser_CheckFileOperations(string condition, OperationType operation, ConditionType cond)
|
||||
{
|
||||
var results = ConditionPatternParser.ParseActionCondition(condition);
|
||||
|
||||
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation , Invert = false };
|
||||
Assert.True(CompareConditions(results, expected));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("!-d", OperationType.Directory, ConditionType.PropertyTest)]
|
||||
[InlineData("!-f", OperationType.RegularFile, ConditionType.PropertyTest)]
|
||||
[InlineData("!-F", OperationType.ExistingFile, ConditionType.PropertyTest)]
|
||||
[InlineData("!-h", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("!-L", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("!-l", OperationType.SymbolicLink, ConditionType.PropertyTest)]
|
||||
[InlineData("!-s", OperationType.Size, ConditionType.PropertyTest)]
|
||||
[InlineData("!-U", OperationType.ExistingUrl, ConditionType.PropertyTest)]
|
||||
[InlineData("!-x", OperationType.Executable, ConditionType.PropertyTest)]
|
||||
public void ConditionParser_CheckFileOperationsInverted(string condition, OperationType operation, ConditionType cond)
|
||||
{
|
||||
var results = ConditionPatternParser.ParseActionCondition(condition);
|
||||
|
||||
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = true };
|
||||
Assert.True(CompareConditions(results, expected));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-gt1", OperationType.Greater, "1", ConditionType.IntComp)]
|
||||
[InlineData("-lt1", OperationType.Less, "1", ConditionType.IntComp)]
|
||||
[InlineData("-ge1", OperationType.GreaterEqual, "1", ConditionType.IntComp)]
|
||||
[InlineData("-le1", OperationType.LessEqual, "1", ConditionType.IntComp)]
|
||||
[InlineData("-eq1", OperationType.Equal, "1", ConditionType.IntComp)]
|
||||
[InlineData("-ne1", OperationType.NotEqual, "1", ConditionType.IntComp)]
|
||||
public void ConditionParser_CheckIntComp(string condition, OperationType operation, string variable, ConditionType cond)
|
||||
{
|
||||
var results = ConditionPatternParser.ParseActionCondition(condition);
|
||||
|
||||
var expected = new ParsedModRewriteExpression { Type = cond, Operation = operation, Invert = false, Operand = variable };
|
||||
Assert.True(CompareConditions(results, expected));
|
||||
}
|
||||
|
||||
// TODO negative tests
|
||||
private bool CompareConditions(ParsedModRewriteExpression i1, ParsedModRewriteExpression i2)
|
||||
{
|
||||
if (i1.Operation != i2.Operation ||
|
||||
i1.Type != i2.Type ||
|
||||
i1.Operand != i2.Operand ||
|
||||
i1.Invert != i2.Invert)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class FlagParserTest
|
||||
{
|
||||
[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);
|
||||
|
||||
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
Assert.True(DictionaryContentsEqual(results.FlagDictionary, expected.FlagDictionary));
|
||||
}
|
||||
|
||||
public bool DictionaryContentsEqual<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IDictionary<TKey, TValue> other)
|
||||
{
|
||||
return (other ?? new Dictionary<TKey, TValue>())
|
||||
.OrderBy(kvp => kvp.Key)
|
||||
.SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
|
||||
.OrderBy(kvp => kvp.Key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>31794f9e-a1aa-4535-b03c-a3233737cd1a</ProjectGuid>
|
||||
<RootNamespace>Microsoft.AspNetCore.Rewrite.Tests</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// 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.ModRewrite;
|
||||
using Microsoft.AspNetCore.Rewrite.Operands;
|
||||
using Xunit;
|
||||
|
||||
namespace RewriteTest
|
||||
{
|
||||
// This file tests an input of a list of tokens and verifies that the appropriate condition is obtained
|
||||
public class ModRewriteConditionBuilderTest
|
||||
{
|
||||
[Fact]
|
||||
public void ConditionBuilder_PassInNoFlagsFlagsEmpty()
|
||||
{
|
||||
var conditionString = "RewriteCond /$1 /hello";
|
||||
var builder = new ConditionBuilder(conditionString);
|
||||
var results = builder.Build();
|
||||
|
||||
//var expected = new Condition(
|
||||
// new Pattern(
|
||||
// new List<PatternSegment>() {
|
||||
// new PatternSegment("/", SegmentType.Literal),
|
||||
// new PatternSegment("1", SegmentType.RuleParameter)
|
||||
// }),
|
||||
// new ConditionExpression { Operand = new RegexOperand {Regex = new Regex("/hello") } },
|
||||
// new ConditionFlags());
|
||||
var expected = (new ConditionBuilder("/$1", "/hello")).Build();
|
||||
|
||||
Assert.True(results.Flags.FlagDictionary.Count == 0);
|
||||
Assert.True(results.Flags.FlagDictionary.Count == expected.Flags.FlagDictionary.Count);
|
||||
Assert.True((results.ConditionExpression.Operand is RegexOperand)
|
||||
&& (expected.ConditionExpression.Operand is RegexOperand));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionBuilder_PassInFlagsFlagsExist()
|
||||
{
|
||||
var conditionString = "RewriteCond /$1 /hello [NC]";
|
||||
var builder = new ConditionBuilder(conditionString);
|
||||
var results = builder.Build();
|
||||
var expected = (new ConditionBuilder("/$1", "/hello", "[NC]")).Build();
|
||||
|
||||
Assert.True(results.Flags.FlagDictionary.Count == 1);
|
||||
Assert.True(results.Flags.FlagDictionary.Count == expected.Flags.FlagDictionary.Count);
|
||||
Assert.True((results.ConditionExpression.Operand is RegexOperand)
|
||||
&& (expected.ConditionExpression.Operand is RegexOperand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class ModRewriteCreatorTest
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
using Microsoft.AspNetCore.Rewrite.Operands;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class ModRewriteFlagTest
|
||||
{
|
||||
// Flag tests
|
||||
[Fact]
|
||||
public void ModRewriteRule_Check403OnForbiddenFlag()
|
||||
{
|
||||
var context = new UrlRewriteContext { HttpContext = CreateRequest("/", "/hey/hello") };
|
||||
var rule = new ModRewriteRule
|
||||
{
|
||||
InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")) , Invert = false },
|
||||
Transform = ConditionTestStringParser.ParseConditionTestString("/$1"),
|
||||
Flags = FlagParser.ParseRuleFlags("[F]")
|
||||
};
|
||||
var res = rule.ApplyRule(context);
|
||||
Assert.True(res.Result == RuleTerminiation.ResponseComplete);
|
||||
Assert.True(context.HttpContext.Response.StatusCode == 403);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModRewriteRule_Check410OnGoneFlag()
|
||||
{
|
||||
var context = new UrlRewriteContext { HttpContext = CreateRequest("/", "/hey/hello") };
|
||||
var rule = new ModRewriteRule
|
||||
{
|
||||
InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false },
|
||||
Transform = ConditionTestStringParser.ParseConditionTestString("/$1"),
|
||||
Flags = FlagParser.ParseRuleFlags("[G]")
|
||||
};
|
||||
var res = rule.ApplyRule(context);
|
||||
Assert.True(res.Result == RuleTerminiation.ResponseComplete);
|
||||
Assert.True(context.HttpContext.Response.StatusCode == 410);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModRewriteRule_CheckLastFlag()
|
||||
{
|
||||
var context = new UrlRewriteContext { HttpContext = CreateRequest("/", "/hey/hello") };
|
||||
var rule = new ModRewriteRule
|
||||
{
|
||||
InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false },
|
||||
Transform = ConditionTestStringParser.ParseConditionTestString("/$1"),
|
||||
Flags = FlagParser.ParseRuleFlags("[L]")
|
||||
};
|
||||
var res = rule.ApplyRule(context);
|
||||
Assert.True(res.Result == RuleTerminiation.StopRules);
|
||||
Assert.True(context.HttpContext.Request.Path.Equals(new PathString("/hello")));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ModRewriteRule_CheckRedirectFlag()
|
||||
{
|
||||
// TODO fix this test.
|
||||
var context = new UrlRewriteContext { HttpContext = CreateRequest("/", "/hey/hello") };
|
||||
var rule = new ModRewriteRule
|
||||
{
|
||||
InitialRule = new RuleExpression { Operand = new RegexOperand(new Regex("/hey/(.*)")), Invert = false },
|
||||
Transform = ConditionTestStringParser.ParseConditionTestString("/$1"),
|
||||
Flags = FlagParser.ParseRuleFlags("[G]")
|
||||
};
|
||||
var res = rule.ApplyRule(context);
|
||||
Assert.True(res.Result == RuleTerminiation.ResponseComplete);
|
||||
Assert.True(context.HttpContext.Response.StatusCode == 410);
|
||||
}
|
||||
|
||||
private HttpContext CreateRequest(string basePath, string requestPath, string requestQuery = "", string hostName = "")
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
context.Request.PathBase = new PathString(basePath);
|
||||
context.Request.Path = new PathString(requestPath);
|
||||
context.Request.QueryString = new QueryString(requestQuery);
|
||||
context.Request.Host = new HostString(hostName);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
// 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.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class ModRewriteMiddlewareTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task Invoke_RewritePathWhenMatching()
|
||||
{
|
||||
var options = new UrlRewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 ");
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("/hey/hello");
|
||||
|
||||
Assert.Equal(response, "/hello");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_RewritePathTerminatesOnFirstSuccessOfRule()
|
||||
{
|
||||
var options = new UrlRewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1 [L]")
|
||||
.AddModRewriteRule("RewriteRule /hello /what");
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("/hey/hello");
|
||||
|
||||
Assert.Equal(response, "/hello");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_RewritePathDoesNotTerminateOnFirstSuccessOfRule()
|
||||
{
|
||||
var options = new UrlRewriteOptions().AddModRewriteRule("RewriteRule /hey/(.*) /$1")
|
||||
.AddModRewriteRule("RewriteRule /hello /what");
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("/hey/hello");
|
||||
|
||||
Assert.Equal(response, "/what");
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("", true)]
|
||||
public void Invoke_StringComparisonTests(string input, bool expected)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_ShouldIgnoreComments()
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader("#RewriteRule ^/hey/(.*) /$1 "));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("/hey/hello");
|
||||
|
||||
Assert.Equal(response, "/hey/hello");
|
||||
}
|
||||
|
||||
// TODO Add tests to check '//' being handled appropriately.
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_ShouldRewriteHomepage()
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/");
|
||||
|
||||
Assert.Equal(response, "/homepage.html");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_ShouldIgnorePorts()
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule ^/$ /homepage.html"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org:42/");
|
||||
|
||||
Assert.Equal(response, "/homepage.html");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_HandleNegatedRewriteRules()
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule !^/$ /homepage.html"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/");
|
||||
|
||||
Assert.Equal(response, "/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_BackReferencesShouldBeApplied()
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader(@"RewriteRule (.*)\.aspx $1.php"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/homepage.aspx");
|
||||
|
||||
Assert.Equal(response, "/homepage.php");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php", "/homepage.php")]
|
||||
[InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php", "/homepage.ASPX")]
|
||||
[InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php [NC]", "/homepage.php")]
|
||||
[InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [NC]", "/homepage.php")]
|
||||
[InlineData("http://www.foo.org/homepage.aspx", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")]
|
||||
[InlineData("http://www.foo.org/homepage.ASPX", @"RewriteRule (.*)\.aspx $1.php [nocase]", "/homepage.php")]
|
||||
public async Task Invoke_ShouldHandleFlagNoCase(string url, string rule, string expected)
|
||||
{
|
||||
var options = new UrlRewriteOptions().ImportFromModRewrite(new StringReader(rule));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync(url);
|
||||
|
||||
Assert.Equal(response, expected);
|
||||
}
|
||||
|
||||
[Fact(Skip="Need to handle escape characters")]
|
||||
public async Task Invoke_HandleMultipleBackReferences()
|
||||
{
|
||||
var options = new UrlRewriteOptions()
|
||||
.ImportFromModRewrite(new StringReader(@"RewriteRule ^/blog/([0-9]+)-([a-z]+) /blog/index.php?archive=$1-$2"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun");
|
||||
|
||||
Assert.Equal(response, @"/blog/index.php?archive=2016-jun");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_CheckFullUrlWithUFlagOnlyPath()
|
||||
{
|
||||
var options = new UrlRewriteOptions()
|
||||
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun");
|
||||
|
||||
Assert.Equal(response, @"/blog/2016-jun/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_CheckFullUrlWithUFlag()
|
||||
{
|
||||
var options = new UrlRewriteOptions()
|
||||
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun");
|
||||
|
||||
Assert.Equal(response, @"http://www.example.com/blog/2016-jun/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_CheckModFileConditions()
|
||||
{
|
||||
var options = new UrlRewriteOptions()
|
||||
.ImportFromModRewrite(new StringReader(@"RewriteRule (.+) http://www.example.com$1/ [U]"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetStringAsync("http://www.foo.org/blog/2016-jun");
|
||||
|
||||
Assert.Equal(response, @"http://www.example.com/blog/2016-jun/");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://www.example.com/foo/")]
|
||||
public async Task Invoke_EnsureHttps(string input)
|
||||
{
|
||||
var options = new UrlRewriteOptions()
|
||||
.ImportFromModRewrite(new StringReader("RewriteCond %{REQUEST_URI} ^foo/ \nRewriteCond %{HTTPS} !on \nRewriteRule ^(.*)$ https://www.example.com$1 [R=301,L,U]"));
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Scheme + "://" + context.Request.Host.Host + context.Request.Path + context.Request.QueryString));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetAsync(input);
|
||||
|
||||
Assert.Equal(response.StatusCode, (HttpStatusCode)301);
|
||||
Assert.Equal(response.Headers.Location.AbsoluteUri, @"https://www.example.com/foo/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class ModRewriteRuleBuilderTest
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.AspNetCore.Rewrite.Tests")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("31794f9e-a1aa-4535-b03c-a3233737cd1a")]
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder.Internal;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class RewriteMiddlewareTests
|
||||
{
|
||||
|
||||
[Theory(Skip = "Adapting to TestServer")]
|
||||
[InlineData("/foo", "", "/foo", "/yes")]
|
||||
[InlineData("/foo", "", "/foo/", "/yes")]
|
||||
[InlineData("/foo", "/Bar", "/foo", "/yes")]
|
||||
[InlineData("/foo", "/Bar", "/foo/cho", "/yes")]
|
||||
[InlineData("/foo", "/Bar", "/foo/cho/", "/yes")]
|
||||
[InlineData("/foo/cho", "/Bar", "/foo/cho", "/yes")]
|
||||
[InlineData("/foo/cho", "/Bar", "/foo/cho/do", "/yes")]
|
||||
public void PathMatchFunc_RewriteDone(string matchPath, string basePath, string requestPath, string rewrite)
|
||||
{
|
||||
var context = CreateRequest(basePath, requestPath);
|
||||
var options = new UrlRewriteOptions().RewritePath(matchPath, rewrite, false);
|
||||
var builder = new ApplicationBuilder(serviceProvider: null)
|
||||
.UseRewriter(options);
|
||||
var app = builder.Build();
|
||||
app.Invoke(context).Wait();
|
||||
Assert.Equal(rewrite, context.Request.Path);
|
||||
}
|
||||
[Theory(Skip = "Adapting to TestServer")]
|
||||
[InlineData(@"/(?<name>\w+)?/(?<id>\w+)?", @"", "/hey/hello", "/${id}/${name}", "/hello/hey")]
|
||||
[InlineData(@"/(?<name>\w+)?/(?<id>\w+)?/(?<temp>\w+)?", @"", "/hey/hello/what", "/${temp}/${id}/${name}", "/what/hello/hey")]
|
||||
public void PathMatchFunc_RegexRewriteDone(string matchPath, string basePath, string requestPath, string rewrite, string expected)
|
||||
{
|
||||
var context = CreateRequest(basePath, requestPath);
|
||||
var options = new UrlRewriteOptions().RewritePath(matchPath, rewrite, false);
|
||||
var builder = new ApplicationBuilder(serviceProvider: null)
|
||||
.UseRewriter(options);
|
||||
|
||||
var app = builder.Build();
|
||||
app.Invoke(context).Wait();
|
||||
Assert.Equal(expected, context.Request.Path);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Adapting to TestServer")]
|
||||
public void PathMatchFunc_RedirectScheme()
|
||||
{
|
||||
HttpContext context = CreateRequest("/", "/");
|
||||
context.Request.Scheme = "http";
|
||||
var options = new UrlRewriteOptions().RedirectScheme(30);
|
||||
var builder = new ApplicationBuilder(serviceProvider: null)
|
||||
.UseRewriter(options);
|
||||
var app = builder.Build();
|
||||
app.Invoke(context).Wait();
|
||||
Assert.True(context.Response.Headers["location"].First().StartsWith("https"));
|
||||
}
|
||||
|
||||
[Theory(Skip = "Adapting to TestServer")]
|
||||
public async Task PathMatchFunc_RewriteScheme()
|
||||
{
|
||||
var options = new UrlRewriteOptions().RewriteScheme();
|
||||
var builder = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseRewriter(options);
|
||||
|
||||
app.Run(context => context.Response.WriteAsync(context.Request.Path));
|
||||
});
|
||||
var server = new TestServer(builder);
|
||||
|
||||
var response = await server.CreateClient().GetAsync("http://foo.com/bar");
|
||||
|
||||
//Assert.True(response.RequestMessage.);
|
||||
}
|
||||
|
||||
|
||||
private HttpContext CreateRequest(string basePath, string requestPath)
|
||||
{
|
||||
HttpContext context = new DefaultHttpContext();
|
||||
context.Request.PathBase = new PathString(basePath);
|
||||
context.Request.Path = new PathString(requestPath);
|
||||
context.Request.Host = new HostString("example.com");
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
using Xunit;
|
||||
namespace Microsoft.AspNetCore.Rewrite
|
||||
{
|
||||
public class RewriteTokenizerTest
|
||||
{
|
||||
[Fact]
|
||||
public void Tokenize_RewriteCondtion()
|
||||
{
|
||||
var testString = "RewriteCond %{HTTPS} !-f";
|
||||
var tokens = Tokenizer.Tokenize(testString);
|
||||
|
||||
var expected = new List<string>();
|
||||
expected.Add("RewriteCond");
|
||||
expected.Add("%{HTTPS}");
|
||||
expected.Add("!-f");
|
||||
Assert.Equal(tokens, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_CheckEscapedSpaceIgnored()
|
||||
{
|
||||
// TODO need consultation on escape characters.
|
||||
var testString = @"RewriteCond %{HTTPS}\ what !-f";
|
||||
var tokens = Tokenizer.Tokenize(testString);
|
||||
|
||||
var expected = new List<string>();
|
||||
expected.Add("RewriteCond");
|
||||
expected.Add(@"%{HTTPS}\ what"); // TODO maybe just have the space here? talking point
|
||||
expected.Add("!-f");
|
||||
Assert.Equal(tokens,expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Rewrite.ModRewrite;
|
||||
using Microsoft.AspNetCore.Rewrite.Operands;
|
||||
using Microsoft.AspNetCore.Rewrite.RuleAbstraction;
|
||||
using Xunit;
|
||||
namespace Microsoft.AspNetCore.Rewrite.Tests.RuleAbstraction
|
||||
{
|
||||
public class RuleRegexParserTest
|
||||
{
|
||||
[Fact]
|
||||
public void RuleRegexParser_ShouldThrowOnNull()
|
||||
{
|
||||
Assert.Throws<FormatException>(() => RuleRegexParser.ParseRuleRegex(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleRegexParser_ShouldThrowOnEmpty()
|
||||
{
|
||||
Assert.Throws<FormatException>(() => RuleRegexParser.ParseRuleRegex(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleRegexParser_RegularRegexExpression()
|
||||
{
|
||||
var results = RuleRegexParser.ParseRuleRegex("(.*)");
|
||||
Assert.False(results.Invert);
|
||||
Assert.Equal(results.Operand, "(.*)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"version": "1.1.0-*",
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
"dependencies": {
|
||||
"dotnet-test-xunit": "2.2.0-*",
|
||||
"Microsoft.AspNetCore.Rewrite": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.TestHost": "1.1.0-*",
|
||||
"Microsoft.Extensions.Logging.Testing": "1.1.0-*",
|
||||
"xunit": "2.2.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0-*",
|
||||
"type": "platform"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net451": {}
|
||||
},
|
||||
"testRunner": "xunit"
|
||||
}
|
||||
Loading…
Reference in New Issue