Skip to content
This repository was archived by the owner on Dec 20, 2018. It is now read-only.

Commit 3faa3de

Browse files
david-peden-q2mikaelm12
authored andcommitted
Add IIS rewrite map support (#168)
1 parent 4fa6ed3 commit 3faa3de

File tree

11 files changed

+323
-13
lines changed

11 files changed

+323
-13
lines changed

src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,4 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextR
6666
return options;
6767
}
6868
}
69-
}
69+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
8+
{
9+
public class IISRewriteMap
10+
{
11+
private readonly Dictionary<string, string> _map = new Dictionary<string, string>();
12+
13+
public IISRewriteMap(string name)
14+
{
15+
if (string.IsNullOrEmpty(name))
16+
{
17+
throw new ArgumentException(nameof(name));
18+
}
19+
Name = name;
20+
}
21+
22+
public string Name { get; }
23+
24+
public string this[string key]
25+
{
26+
get
27+
{
28+
string value;
29+
return _map.TryGetValue(key, out value) ? value : null;
30+
}
31+
set
32+
{
33+
if (string.IsNullOrEmpty(key))
34+
{
35+
throw new ArgumentException(nameof(key));
36+
}
37+
if (string.IsNullOrEmpty(value))
38+
{
39+
throw new ArgumentException(nameof(value));
40+
}
41+
_map[key] = value;
42+
}
43+
}
44+
}
45+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
8+
{
9+
public class IISRewriteMapCollection : IEnumerable<IISRewriteMap>
10+
{
11+
private readonly Dictionary<string, IISRewriteMap> _rewriteMaps = new Dictionary<string, IISRewriteMap>();
12+
13+
public void Add(IISRewriteMap rewriteMap)
14+
{
15+
if (rewriteMap != null)
16+
{
17+
_rewriteMaps[rewriteMap.Name] = rewriteMap;
18+
}
19+
}
20+
21+
public int Count => _rewriteMaps.Count;
22+
23+
public IISRewriteMap this[string key]
24+
{
25+
get
26+
{
27+
IISRewriteMap value;
28+
return _rewriteMaps.TryGetValue(key, out value) ? value : null;
29+
}
30+
}
31+
32+
IEnumerator IEnumerable.GetEnumerator()
33+
{
34+
return _rewriteMaps.Values.GetEnumerator();
35+
}
36+
37+
public IEnumerator<IISRewriteMap> GetEnumerator()
38+
{
39+
return _rewriteMaps.Values.GetEnumerator();
40+
}
41+
}
42+
}

src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ public class InputParser
1212
private const char Colon = ':';
1313
private const char OpenBrace = '{';
1414
private const char CloseBrace = '}';
15+
private readonly IISRewriteMapCollection _rewriteMaps;
16+
17+
public InputParser()
18+
{
19+
}
20+
21+
public InputParser(IISRewriteMapCollection rewriteMaps)
22+
{
23+
_rewriteMaps = rewriteMaps;
24+
}
1525

1626
/// <summary>
1727
/// Creates a pattern, which is a template to create a new test string to
@@ -31,7 +41,7 @@ public Pattern ParseInputString(string testString, bool global)
3141
return ParseString(context, global);
3242
}
3343

34-
private static Pattern ParseString(ParserContext context, bool global)
44+
private Pattern ParseString(ParserContext context, bool global)
3545
{
3646
var results = new List<PatternSegment>();
3747
while (context.Next())
@@ -60,7 +70,7 @@ private static Pattern ParseString(ParserContext context, bool global)
6070
return new Pattern(results);
6171
}
6272

63-
private static void ParseParameter(ParserContext context, IList<PatternSegment> results, bool global)
73+
private void ParseParameter(ParserContext context, IList<PatternSegment> results, bool global)
6474
{
6575
context.Mark();
6676
// Four main cases:
@@ -128,6 +138,13 @@ private static void ParseParameter(ParserContext context, IList<PatternSegment>
128138
return;
129139
}
130140
default:
141+
var rewriteMap = _rewriteMaps?[parameter];
142+
if (rewriteMap != null)
143+
{
144+
var pattern = ParseString(context, global);
145+
results.Add(new RewriteMapSegment(rewriteMap, pattern));
146+
return;
147+
}
131148
throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(parameter, context.Index));
132149
}
133150
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Linq;
6+
using System.Xml.Linq;
7+
8+
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
9+
{
10+
public static class RewriteMapParser
11+
{
12+
public static IISRewriteMapCollection Parse(XElement xmlRoot)
13+
{
14+
if (xmlRoot == null)
15+
{
16+
throw new ArgumentNullException(nameof(xmlRoot));
17+
}
18+
19+
var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault();
20+
if (mapsElement == null)
21+
{
22+
return null;
23+
}
24+
25+
var rewriteMaps = new IISRewriteMapCollection();
26+
foreach (var mapElement in mapsElement.Elements(RewriteTags.RewriteMap))
27+
{
28+
var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value);
29+
foreach (var addElement in mapElement.Elements(RewriteTags.Add))
30+
{
31+
map[addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant()] = addElement.Attribute(RewriteTags.Value).Value;
32+
}
33+
rewriteMaps.Add(map);
34+
}
35+
36+
return rewriteMaps;
37+
}
38+
}
39+
}

src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public static class RewriteTags
1313
public const string GlobalRules = "globalRules";
1414
public const string IgnoreCase = "ignoreCase";
1515
public const string Input = "input";
16+
public const string Key = "key";
1617
public const string LogicalGrouping = "logicalGrouping";
1718
public const string LogRewrittenUrl = "logRewrittenUrl";
1819
public const string Match = "match";
@@ -22,13 +23,16 @@ public static class RewriteTags
2223
public const string Negate = "negate";
2324
public const string Pattern = "pattern";
2425
public const string PatternSyntax = "patternSyntax";
25-
public const string Rewrite = "rewrite";
2626
public const string RedirectType = "redirectType";
27+
public const string Rewrite = "rewrite";
28+
public const string RewriteMap = "rewriteMap";
29+
public const string RewriteMaps = "rewriteMaps";
2730
public const string Rule = "rule";
2831
public const string Rules = "rules";
2932
public const string StopProcessing = "stopProcessing";
3033
public const string TrackAllCaptures = "trackAllCaptures";
3134
public const string Type = "type";
3235
public const string Url = "url";
36+
public const string Value = "value";
3337
}
3438
}

src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,28 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
1212
{
1313
public class UrlRewriteFileParser
1414
{
15-
private readonly InputParser _inputParser = new InputParser();
15+
private InputParser _inputParser;
1616

17+
/// <summary>
18+
/// Parse an IIS rewrite section into a list of <see cref="IISUrlRewriteRule"/>s.
19+
/// </summary>
20+
/// <param name="reader">The reader containing the rewrite XML</param>
1721
public IList<IISUrlRewriteRule> Parse(TextReader reader)
1822
{
1923
var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo);
2024
var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault();
2125

22-
if (xmlRoot != null)
26+
if (xmlRoot == null)
2327
{
24-
var result = new List<IISUrlRewriteRule>();
25-
ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result, global: true);
26-
ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result, global: false);
27-
return result;
28+
return null;
2829
}
29-
return null;
30+
31+
_inputParser = new InputParser(RewriteMapParser.Parse(xmlRoot));
32+
33+
var result = new List<IISUrlRewriteRule>();
34+
ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result, global: true);
35+
ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result, global: false);
36+
return result;
3037
}
3138

3239
private void ParseRules(XElement rules, IList<IISUrlRewriteRule> result, bool global)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
5+
6+
namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments
7+
{
8+
public class RewriteMapSegment : PatternSegment
9+
{
10+
private readonly IISRewriteMap _rewriteMap;
11+
private readonly Pattern _pattern;
12+
13+
public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern)
14+
{
15+
_rewriteMap = rewriteMap;
16+
_pattern = pattern;
17+
}
18+
19+
public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences)
20+
{
21+
var key = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences).ToLowerInvariant();
22+
return _rewriteMap[key];
23+
}
24+
}
25+
}

test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Linq;
56
using System.Text.RegularExpressions;
67
using Microsoft.AspNetCore.Http;
78
using Microsoft.AspNetCore.Rewrite.Internal;
89
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite;
10+
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments;
11+
using Microsoft.Extensions.Logging.Testing;
912
using Xunit;
1013

1114
namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite
@@ -88,11 +91,48 @@ public void FormatExceptionsOnBadSyntax(string testString)
8891
Assert.Throws<FormatException>(() => new InputParser().ParseInputString(testString, global: false));
8992
}
9093

91-
private RewriteContext CreateTestRewriteContext()
94+
[Fact]
95+
public void Should_throw_FormatException_if_no_rewrite_maps_are_defined()
96+
{
97+
Assert.Throws<FormatException>(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}", global: false));
98+
}
99+
100+
[Fact]
101+
public void Should_throw_FormatException_if_rewrite_map_not_found()
92102
{
103+
const string definedMapName = "testMap";
104+
const string undefinedMapName = "apiMap";
105+
var map = new IISRewriteMap(definedMapName);
106+
var maps = new IISRewriteMapCollection { map };
107+
Assert.Throws<FormatException>(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}", global: false));
108+
}
109+
110+
[Fact]
111+
public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result()
112+
{
113+
const string expectedMapName = "apiMap";
114+
const string expectedKey = "api.test.com";
115+
const string expectedValue = "test.com/api";
116+
var map = new IISRewriteMap(expectedMapName);
117+
map[expectedKey] = expectedValue;
118+
var maps = new IISRewriteMapCollection { map };
119+
120+
var inputString = $"{{{expectedMapName}:{{R:1}}}}";
121+
var pattern = new InputParser(maps).ParseInputString(inputString, global: false);
122+
Assert.Equal(1, pattern.PatternSegments.Count);
93123

124+
var segment = pattern.PatternSegments.Single();
125+
var rewriteMapSegment = segment as RewriteMapSegment;
126+
Assert.NotNull(rewriteMapSegment);
127+
128+
var result = rewriteMapSegment.Evaluate(CreateTestRewriteContext(), CreateRewriteMapRuleMatch(expectedKey).BackReferences, CreateRewriteMapConditionMatch(inputString).BackReferences);
129+
Assert.Equal(expectedValue, result);
130+
}
131+
132+
private RewriteContext CreateTestRewriteContext()
133+
{
94134
var context = new DefaultHttpContext();
95-
return new RewriteContext { HttpContext = context, StaticFileProvider = null };
135+
return new RewriteContext { HttpContext = context, StaticFileProvider = null, Logger = new NullLogger() };
96136
}
97137

98138
private BackReferenceCollection CreateTestRuleBackReferences()
@@ -106,5 +146,17 @@ private BackReferenceCollection CreateTestCondBackReferences()
106146
var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)");
107147
return new BackReferenceCollection(match.Groups);
108148
}
149+
150+
private MatchResults CreateRewriteMapRuleMatch(string input)
151+
{
152+
var match = Regex.Match(input, "([^/]*)/?(.*)");
153+
return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
154+
}
155+
156+
private MatchResults CreateRewriteMapConditionMatch(string input)
157+
{
158+
var match = Regex.Match(input, "(.+)");
159+
return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success };
160+
}
109161
}
110162
}

0 commit comments

Comments
 (0)