From 30b4b75ae5883d630dabe9c94817854bf5dbfa00 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Fri, 4 Nov 2016 11:03:39 -0500 Subject: [PATCH 01/19] #55 add rewrite map support to IIS --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 38 +++++++++++++++++++ .../Internal/IISUrlRewrite/InputParser.cs | 17 ++++++++- .../Internal/IISUrlRewrite/RewriteTags.cs | 6 ++- .../IISUrlRewrite/UrlRewriteFileParser.cs | 26 ++++++++++++- .../PatternSegments/RewriteMapSegment.cs | 32 ++++++++++++++++ 5 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs new file mode 100644 index 00000000..6293d9d3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite +{ + public class IISRewriteMap + { + private readonly Dictionary _map = new Dictionary(); + public string Name { get; } + + public IISRewriteMap(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("name cannot be empty or null", nameof(name)); + } + Name = name; + } + + public void AddOrUpdateEntry(string key, string value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException($"{nameof(key)} cannot be empty or null", nameof(key)); + } + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException($"{nameof(value)} cannot be empty or null", nameof(value)); + } + _map[key] = value; + } + + public bool TryGetEntry(string key, out string value) + { + return _map.TryGetValue(key, out value); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index 8a944972..4712e6dd 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -12,6 +12,12 @@ public class InputParser private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; + private readonly IDictionary _rewriteMaps; + + public InputParser(IDictionary rewriteMaps = null) + { + _rewriteMaps = rewriteMaps; + } /// /// Creates a pattern, which is a template to create a new test string to @@ -30,7 +36,7 @@ public Pattern ParseInputString(string testString) return ParseString(context); } - private static Pattern ParseString(ParserContext context) + private Pattern ParseString(ParserContext context) { var results = new List(); while (context.Next()) @@ -59,7 +65,7 @@ private static Pattern ParseString(ParserContext context) return new Pattern(results); } - private static void ParseParameter(ParserContext context, IList results) + private void ParseParameter(ParserContext context, IList results) { context.Mark(); // Four main cases: @@ -127,6 +133,13 @@ private static void ParseParameter(ParserContext context, IList return; } default: + IISRewriteMap rewriteMap; + if (_rewriteMaps != null && _rewriteMaps.TryGetValue(parameter, out rewriteMap)) + { + Pattern pattern = ParseString(context); + results.Add(new RewriteMapSegment(rewriteMap, pattern)); + return; + } throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(parameter, context.Index)); } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs index 9b62b265..42a8e5aa 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteTags.cs @@ -13,6 +13,7 @@ public static class RewriteTags public const string GlobalRules = "globalRules"; public const string IgnoreCase = "ignoreCase"; public const string Input = "input"; + public const string Key = "key"; public const string LogicalGrouping = "logicalGrouping"; public const string LogRewrittenUrl = "logRewrittenUrl"; public const string Match = "match"; @@ -22,13 +23,16 @@ public static class RewriteTags public const string Negate = "negate"; public const string Pattern = "pattern"; public const string PatternSyntax = "patternSyntax"; - public const string Rewrite = "rewrite"; public const string RedirectType = "redirectType"; + public const string Rewrite = "rewrite"; + public const string RewriteMap = "rewriteMap"; + public const string RewriteMaps = "rewriteMaps"; public const string Rule = "rule"; public const string Rules = "rules"; public const string StopProcessing = "stopProcessing"; public const string TrackAllCaptures = "trackAllCaptures"; public const string Type = "type"; public const string Url = "url"; + public const string Value = "value"; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index c8d20bed..745b056f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class UrlRewriteFileParser { - private readonly InputParser _inputParser = new InputParser(); + private InputParser _inputParser; public IList Parse(TextReader reader) { @@ -21,6 +21,8 @@ public IList Parse(TextReader reader) if (xmlRoot != null) { + _inputParser = new InputParser(ParseRewriteMaps(xmlRoot)); + var result = new List(); // TODO Global rules are currently not treated differently than normal rules, fix. // See: https://github.com/aspnet/BasicMiddleware/issues/59 @@ -31,6 +33,28 @@ public IList Parse(TextReader reader) return null; } + private IDictionary ParseRewriteMaps(XElement xmlRoot) + { + var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); + if (mapsElement == null) + { + return null; + } + + var rewriteMaps = new Dictionary(); + foreach (XElement mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) + { + var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); + foreach (XElement addElement in mapElement.Elements(RewriteTags.Add)) + { + map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key)?.Value, addElement.Attribute(RewriteTags.Value)?.Value); + } + rewriteMaps.Add(map.Name, map); + } + + return rewriteMaps; + } + private void ParseRules(XElement rules, IList result) { if (rules == null) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs new file mode 100644 index 00000000..d7440968 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -0,0 +1,32 @@ +using System; + +using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments +{ + public class RewriteMapSegment : PatternSegment + { + private readonly IISRewriteMap _rewriteMap; + private readonly Pattern _pattern; + + public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) + { + _rewriteMap = rewriteMap; + _pattern = pattern; + } + + public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + { + string key = _pattern.Evaluate(context, ruleMatch, condMatch); + string value; + if (_rewriteMap.TryGetEntry(key, out value)) + { + return value; + } + var exception = new Exception($"Rewrite map entry not found: '{key}'"); + context.Logger.LogError(context.HttpContext.TraceIdentifier, exception); + throw exception; + } + } +} \ No newline at end of file From 2adedfc220b816cc2bfd1ad673eaec877edb1ac4 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Fri, 4 Nov 2016 14:18:52 -0500 Subject: [PATCH 02/19] add tests for rewrite maps --- .../IISUrlRewrite/RewriteMapParser.cs | 31 ++++++++++ .../IISUrlRewrite/UrlRewriteFileParser.cs | 25 +------- .../IISUrlRewrite/InputParserTests.cs | 58 ++++++++++++++++++- .../IISUrlRewrite/RewriteMapParserTests.cs | 47 +++++++++++++++ 4 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs create mode 100644 test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs new file mode 100644 index 00000000..530298f0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite +{ + public class RewriteMapParser + { + public IDictionary Parse(XElement xmlRoot) + { + var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); + if (mapsElement == null) + { + return null; + } + + var rewriteMaps = new Dictionary(); + foreach (XElement mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) + { + var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); + foreach (XElement addElement in mapElement.Elements(RewriteTags.Add)) + { + map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key)?.Value, addElement.Attribute(RewriteTags.Value)?.Value); + } + rewriteMaps.Add(map.Name, map); + } + + return rewriteMaps; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index 745b056f..abaa4e70 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -21,7 +21,8 @@ public IList Parse(TextReader reader) if (xmlRoot != null) { - _inputParser = new InputParser(ParseRewriteMaps(xmlRoot)); + var rewriteMapParser = new RewriteMapParser(); + _inputParser = new InputParser(rewriteMapParser.Parse(xmlRoot)); var result = new List(); // TODO Global rules are currently not treated differently than normal rules, fix. @@ -33,28 +34,6 @@ public IList Parse(TextReader reader) return null; } - private IDictionary ParseRewriteMaps(XElement xmlRoot) - { - var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); - if (mapsElement == null) - { - return null; - } - - var rewriteMaps = new Dictionary(); - foreach (XElement mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) - { - var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); - foreach (XElement addElement in mapElement.Elements(RewriteTags.Add)) - { - map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key)?.Value, addElement.Attribute(RewriteTags.Value)?.Value); - } - rewriteMaps.Add(map.Name, map); - } - - return rewriteMaps; - } - private void ParseRules(XElement rules, IList result) { if (rules == null) diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index df59ae45..251e61c7 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -2,10 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite.Internal; using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; +using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; +using Microsoft.Extensions.Logging.Testing; + using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite @@ -88,11 +93,48 @@ public void FormatExceptionsOnBadSyntax(string testString) Assert.Throws(() => new InputParser().ParseInputString(testString)); } - private RewriteContext CreateTestRewriteContext() + [Fact] + public void Should_throw_FormatException_if_no_rewrite_maps_are_defined() + { + Assert.Throws(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}")); + } + + [Fact] + public void Should_throw_FormatException_if_rewrite_map_not_found() { + const string definedMapName = "testMap"; + const string undefinedMapName = "apiMap"; + var map = new IISRewriteMap(definedMapName); + var maps = new Dictionary { { map.Name, map } }; + Assert.Throws(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}")); + } + + [Fact] + public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() + { + const string expectedMapName = "apiMap"; + const string expectedKey = "api.test.com"; + const string expectedValue = "test.com/api"; + var map = new IISRewriteMap(expectedMapName); + map.AddOrUpdateEntry(expectedKey, expectedValue); + var maps = new Dictionary { { map.Name, map } }; + + string inputString = $"{{{expectedMapName}:{{R:1}}}}"; + Pattern pattern = new InputParser(maps).ParseInputString(inputString); + Assert.Equal(1, pattern.PatternSegments.Count); + + PatternSegment segment = pattern.PatternSegments.Single(); + var rewriteMapSegment = segment as RewriteMapSegment; + Assert.NotNull(rewriteMapSegment); + var result = rewriteMapSegment.Evaluate(CreateTestRewriteContext(), CreateRewriteMapRuleMatch(expectedKey), CreateRewriteMapConditionMatch(inputString)); + Assert.Equal(expectedValue, result); + } + + private RewriteContext CreateTestRewriteContext() + { var context = new DefaultHttpContext(); - return new RewriteContext { HttpContext = context, StaticFileProvider = null }; + return new RewriteContext { HttpContext = context, StaticFileProvider = null, Logger = new NullLogger() }; } private MatchResults CreateTestRuleMatch() @@ -106,5 +148,17 @@ private MatchResults CreateTestCondMatch() var match = Regex.Match("foo/bar/baz", "(.*)/(.*)/(.*)"); return new MatchResults { BackReference = match.Groups, Success = match.Success }; } + + private MatchResults CreateRewriteMapRuleMatch(string input) + { + var match = Regex.Match(input, "([^/]*)/?(.*)"); + return new MatchResults { BackReference = match.Groups, Success = match.Success }; + } + + private MatchResults CreateRewriteMapConditionMatch(string input) + { + var match = Regex.Match(input, "(.+)"); + return new MatchResults { BackReference = match.Groups, Success = match.Success }; + } } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs new file mode 100644 index 00000000..0eb801ef --- /dev/null +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; + +using Xunit; + +namespace Microsoft.AspNetCore.Rewrite.Tests.IISUrlRewrite +{ + public class RewriteMapParserTests + { + [Fact] + public void Should_parse_rewrite_map() + { + // arrange + const string expectedMapName = "apiMap"; + const string expectedKey = "api.test.com"; + const string expectedValue = "test.com/api"; + string xml = $@" + + + + + + "; + + // act + XDocument xmlDoc = XDocument.Load(new StringReader(xml), LoadOptions.SetLineInfo); + XElement xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); + IDictionary actualMaps = new RewriteMapParser().Parse(xmlRoot); + + // assert + Assert.Equal(1, actualMaps.Count); + + IISRewriteMap actualMap; + Assert.True(actualMaps.TryGetValue(expectedMapName, out actualMap)); + Assert.NotNull(actualMap); + Assert.Equal(expectedMapName, actualMap.Name); + + string actualValue; + Assert.True(actualMap.TryGetEntry(expectedKey, out actualValue)); + Assert.Equal(expectedValue, actualValue); + } + } +} \ No newline at end of file From 7d0ad0f90caf5f5373e7a46b9ffc03f1ec3efda0 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Tue, 8 Nov 2016 16:10:17 -0600 Subject: [PATCH 03/19] code review feedback --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 58 +++++++++---------- .../Internal/IISUrlRewrite/InputParser.cs | 2 +- .../IISUrlRewrite/RewriteMapParser.cs | 6 +- .../PatternSegments/RewriteMapSegment.cs | 15 +---- 4 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs index 6293d9d3..4e7d4159 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -3,36 +3,36 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { - public class IISRewriteMap - { - private readonly Dictionary _map = new Dictionary(); - public string Name { get; } + public class IISRewriteMap + { + private readonly Dictionary _map = new Dictionary(); + public string Name { get; } - public IISRewriteMap(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("name cannot be empty or null", nameof(name)); - } - Name = name; - } + public IISRewriteMap(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(nameof(name)); + } + Name = name; + } - public void AddOrUpdateEntry(string key, string value) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException($"{nameof(key)} cannot be empty or null", nameof(key)); - } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException($"{nameof(value)} cannot be empty or null", nameof(value)); - } - _map[key] = value; - } + public void AddOrUpdateEntry(string key, string value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException(nameof(key)); + } + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(nameof(value)); + } + _map[key] = value; + } - public bool TryGetEntry(string key, out string value) - { - return _map.TryGetValue(key, out value); - } - } + public bool TryGetEntry(string key, out string value) + { + return _map.TryGetValue(key, out value); + } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index 4712e6dd..2ebfce81 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -136,7 +136,7 @@ private void ParseParameter(ParserContext context, IList results IISRewriteMap rewriteMap; if (_rewriteMaps != null && _rewriteMaps.TryGetValue(parameter, out rewriteMap)) { - Pattern pattern = ParseString(context); + var pattern = ParseString(context); results.Add(new RewriteMapSegment(rewriteMap, pattern)); return; } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 530298f0..c130b19a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -15,12 +15,12 @@ public IDictionary Parse(XElement xmlRoot) } var rewriteMaps = new Dictionary(); - foreach (XElement mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) + foreach (var mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) { var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); - foreach (XElement addElement in mapElement.Elements(RewriteTags.Add)) + foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { - map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key)?.Value, addElement.Attribute(RewriteTags.Value)?.Value); + map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key).Value, addElement.Attribute(RewriteTags.Value).Value); } rewriteMaps.Add(map.Name, map); } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs index d7440968..17ed1c77 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -1,7 +1,4 @@ -using System; - -using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; -using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { @@ -18,15 +15,9 @@ public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - string key = _pattern.Evaluate(context, ruleMatch, condMatch); + var key = _pattern.Evaluate(context, ruleMatch, condMatch); string value; - if (_rewriteMap.TryGetEntry(key, out value)) - { - return value; - } - var exception = new Exception($"Rewrite map entry not found: '{key}'"); - context.Logger.LogError(context.HttpContext.TraceIdentifier, exception); - throw exception; + return _rewriteMap.TryGetEntry(key, out value) ? value : null; } } } \ No newline at end of file From d428eb9d9abe8d71e03d4a0d9e0315d20d329233 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 5 Dec 2016 12:04:29 -0600 Subject: [PATCH 04/19] ensure lookups in rewrite maps are case-insensitive --- .../Internal/IISUrlRewrite/RewriteMapParser.cs | 2 +- .../Internal/PatternSegments/RewriteMapSegment.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index c130b19a..663d92c5 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -20,7 +20,7 @@ public IDictionary Parse(XElement xmlRoot) var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { - map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key).Value, addElement.Attribute(RewriteTags.Value).Value); + map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant(), addElement.Attribute(RewriteTags.Value).Value); } rewriteMaps.Add(map.Name, map); } diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs index 17ed1c77..f5e59e01 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -15,7 +15,7 @@ public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { - var key = _pattern.Evaluate(context, ruleMatch, condMatch); + var key = _pattern.Evaluate(context, ruleMatch, condMatch).ToLowerInvariant(); string value; return _rewriteMap.TryGetEntry(key, out value) ? value : null; } From 5faa822247d01ffe4197a5dd0139c4dbaa403c76 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 5 Dec 2016 16:00:51 -0600 Subject: [PATCH 05/19] code review feedback --- .../Internal/IISUrlRewrite/RewriteMapParser.cs | 10 ++++++++-- .../Internal/IISUrlRewrite/UrlRewriteFileParser.cs | 3 +-- .../IISUrlRewrite/RewriteMapParserTests.cs | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 663d92c5..8aa9bfe3 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -1,13 +1,19 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { - public class RewriteMapParser + public static class RewriteMapParser { - public IDictionary Parse(XElement xmlRoot) + public static IDictionary Parse(XElement xmlRoot) { + if (xmlRoot == null) + { + throw new ArgumentException(nameof(xmlRoot)); + } + var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); if (mapsElement == null) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index abaa4e70..1260a682 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -21,8 +21,7 @@ public IList Parse(TextReader reader) if (xmlRoot != null) { - var rewriteMapParser = new RewriteMapParser(); - _inputParser = new InputParser(rewriteMapParser.Parse(xmlRoot)); + _inputParser = new InputParser(RewriteMapParser.Parse(xmlRoot)); var result = new List(); // TODO Global rules are currently not treated differently than normal rules, fix. diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs index 0eb801ef..79e01590 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs @@ -18,7 +18,7 @@ public void Should_parse_rewrite_map() const string expectedMapName = "apiMap"; const string expectedKey = "api.test.com"; const string expectedValue = "test.com/api"; - string xml = $@" + var xml = $@" @@ -27,9 +27,9 @@ public void Should_parse_rewrite_map() "; // act - XDocument xmlDoc = XDocument.Load(new StringReader(xml), LoadOptions.SetLineInfo); - XElement xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); - IDictionary actualMaps = new RewriteMapParser().Parse(xmlRoot); + var xmlDoc = XDocument.Load(new StringReader(xml), LoadOptions.SetLineInfo); + var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); + var actualMaps = RewriteMapParser.Parse(xmlRoot); // assert Assert.Equal(1, actualMaps.Count); From 224665afa058326e30f63c4ccb680426e35f4b7e Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 12 Dec 2016 16:55:30 -0600 Subject: [PATCH 06/19] code review feedback --- .../Internal/IISUrlRewrite/InputParser.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index 2ebfce81..984fcacc 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -14,7 +14,11 @@ public class InputParser private const char CloseBrace = '}'; private readonly IDictionary _rewriteMaps; - public InputParser(IDictionary rewriteMaps = null) + public InputParser() + { + } + + public InputParser(IDictionary rewriteMaps) { _rewriteMaps = rewriteMaps; } From 24f2ad720c53452b8ffe1d60d21424a957ff8b0d Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 12 Dec 2016 16:56:46 -0600 Subject: [PATCH 07/19] code review feedback --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 2 +- .../Internal/IISUrlRewrite/RewriteMapParser.cs | 2 +- .../IISUrlRewrite/InputParserTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs index 4e7d4159..a404a256 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -17,7 +17,7 @@ public IISRewriteMap(string name) Name = name; } - public void AddOrUpdateEntry(string key, string value) + public void SetEntry(string key, string value) { if (string.IsNullOrEmpty(key)) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 8aa9bfe3..90ad9f98 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -26,7 +26,7 @@ public static IDictionary Parse(XElement xmlRoot) var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { - map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant(), addElement.Attribute(RewriteTags.Value).Value); + map.SetEntry(addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant(), addElement.Attribute(RewriteTags.Value).Value); } rewriteMaps.Add(map.Name, map); } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index 251e61c7..bbdc0741 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -116,7 +116,7 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() const string expectedKey = "api.test.com"; const string expectedValue = "test.com/api"; var map = new IISRewriteMap(expectedMapName); - map.AddOrUpdateEntry(expectedKey, expectedValue); + map.SetEntry(expectedKey, expectedValue); var maps = new Dictionary { { map.Name, map } }; string inputString = $"{{{expectedMapName}:{{R:1}}}}"; From 87c900a9fe9be772153e8381ccdbf95882c00b3b Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 12 Dec 2016 17:03:54 -0600 Subject: [PATCH 08/19] code review feedback --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 8 ++++++-- .../Internal/PatternSegments/RewriteMapSegment.cs | 3 +-- .../IISUrlRewrite/RewriteMapParserTests.cs | 5 +---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs index a404a256..97d664c1 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -30,9 +30,13 @@ public void SetEntry(string key, string value) _map[key] = value; } - public bool TryGetEntry(string key, out string value) + public string this[string key] { - return _map.TryGetValue(key, out value); + get + { + string value; + return _map.TryGetValue(key, out value) ? value : null; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs index f5e59e01..afddfdd0 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -16,8 +16,7 @@ public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) { var key = _pattern.Evaluate(context, ruleMatch, condMatch).ToLowerInvariant(); - string value; - return _rewriteMap.TryGetEntry(key, out value) ? value : null; + return _rewriteMap[key]; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs index 79e01590..8a86123c 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs @@ -38,10 +38,7 @@ public void Should_parse_rewrite_map() Assert.True(actualMaps.TryGetValue(expectedMapName, out actualMap)); Assert.NotNull(actualMap); Assert.Equal(expectedMapName, actualMap.Name); - - string actualValue; - Assert.True(actualMap.TryGetEntry(expectedKey, out actualValue)); - Assert.Equal(expectedValue, actualValue); + Assert.Equal(expectedValue, actualMap[expectedKey]); } } } \ No newline at end of file From c2b536d41d34ec624b90428b4d145741805e70fd Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 12 Dec 2016 17:12:01 -0600 Subject: [PATCH 09/19] code review feedback --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 26 +++++++++---------- .../IISUrlRewrite/RewriteMapParser.cs | 2 +- .../IISUrlRewrite/InputParserTests.cs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs index 97d664c1..75440929 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -6,7 +6,6 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite public class IISRewriteMap { private readonly Dictionary _map = new Dictionary(); - public string Name { get; } public IISRewriteMap(string name) { @@ -17,18 +16,7 @@ public IISRewriteMap(string name) Name = name; } - public void SetEntry(string key, string value) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException(nameof(key)); - } - if (string.IsNullOrEmpty(value)) - { - throw new ArgumentException(nameof(value)); - } - _map[key] = value; - } + public string Name { get; } public string this[string key] { @@ -37,6 +25,18 @@ public string this[string key] string value; return _map.TryGetValue(key, out value) ? value : null; } + set + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException(nameof(key)); + } + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(nameof(value)); + } + _map[key] = value; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 90ad9f98..436720fe 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -26,7 +26,7 @@ public static IDictionary Parse(XElement xmlRoot) var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { - map.SetEntry(addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant(), addElement.Attribute(RewriteTags.Value).Value); + map[addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant()] = addElement.Attribute(RewriteTags.Value).Value; } rewriteMaps.Add(map.Name, map); } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index bbdc0741..df14f376 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -116,7 +116,7 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() const string expectedKey = "api.test.com"; const string expectedValue = "test.com/api"; var map = new IISRewriteMap(expectedMapName); - map.SetEntry(expectedKey, expectedValue); + map[expectedKey] = expectedValue; var maps = new Dictionary { { map.Name, map } }; string inputString = $"{{{expectedMapName}:{{R:1}}}}"; From bfae4685aba970d7fc8da71790d9b5f747cce3c4 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 9 Jan 2017 10:32:42 -0600 Subject: [PATCH 10/19] fix breakages after merging aspnet/basicmiddleware --- samples/RewriteSample/project.json | 2 +- .../Internal/PatternSegments/RewriteMapSegment.cs | 4 ++-- src/Microsoft.AspNetCore.Rewrite/project.json | 2 +- .../IISUrlRewrite/InputParserTests.cs | 6 +++--- test/Microsoft.AspNetCore.Rewrite.Tests/project.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/RewriteSample/project.json b/samples/RewriteSample/project.json index fdfd4c81..5066193e 100644 --- a/samples/RewriteSample/project.json +++ b/samples/RewriteSample/project.json @@ -1,6 +1,6 @@ { "dependencies": { - "Microsoft.AspNetCore.Rewrite": "1.1.0-*", + "Microsoft.AspNetCore.Rewrite": "1.2.0-*", "Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*", "Microsoft.AspNetCore.Server.Kestrel.Https": "1.2.0-*" }, diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs index afddfdd0..ea0def1a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -13,9 +13,9 @@ public RewriteMapSegment(IISRewriteMap rewriteMap, Pattern pattern) _pattern = pattern; } - public override string Evaluate(RewriteContext context, MatchResults ruleMatch, MatchResults condMatch) + public override string Evaluate(RewriteContext context, BackReferenceCollection ruleBackReferences, BackReferenceCollection conditionBackReferences) { - var key = _pattern.Evaluate(context, ruleMatch, condMatch).ToLowerInvariant(); + var key = _pattern.Evaluate(context, ruleBackReferences, conditionBackReferences).ToLowerInvariant(); return _rewriteMap[key]; } } diff --git a/src/Microsoft.AspNetCore.Rewrite/project.json b/src/Microsoft.AspNetCore.Rewrite/project.json index 0b9bb3a5..ede20b49 100644 --- a/src/Microsoft.AspNetCore.Rewrite/project.json +++ b/src/Microsoft.AspNetCore.Rewrite/project.json @@ -1,5 +1,5 @@ { - "version": "1.1.0-*", + "version": "1.2.0-*", "buildOptions": { "warningsAsErrors": true, "keyFile": "../../tools/Key.snk", diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index b658c934..5a893d4d 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -127,7 +127,7 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() var rewriteMapSegment = segment as RewriteMapSegment; Assert.NotNull(rewriteMapSegment); - var result = rewriteMapSegment.Evaluate(CreateTestRewriteContext(), CreateRewriteMapRuleMatch(expectedKey), CreateRewriteMapConditionMatch(inputString)); + var result = rewriteMapSegment.Evaluate(CreateTestRewriteContext(), CreateRewriteMapRuleMatch(expectedKey).BackReferences, CreateRewriteMapConditionMatch(inputString).BackReferences); Assert.Equal(expectedValue, result); } @@ -152,13 +152,13 @@ private BackReferenceCollection CreateTestCondBackReferences() private MatchResults CreateRewriteMapRuleMatch(string input) { var match = Regex.Match(input, "([^/]*)/?(.*)"); - return new MatchResults { BackReference = match.Groups, Success = match.Success }; + return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } private MatchResults CreateRewriteMapConditionMatch(string input) { var match = Regex.Match(input, "(.+)"); - return new MatchResults { BackReference = match.Groups, Success = match.Success }; + return new MatchResults { BackReferences = new BackReferenceCollection(match.Groups), Success = match.Success }; } } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/project.json b/test/Microsoft.AspNetCore.Rewrite.Tests/project.json index 16c8c03f..9368d9a4 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/project.json +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/project.json @@ -6,7 +6,7 @@ "dependencies": { "dotnet-test-xunit": "2.2.0-*", "Microsoft.AspNetCore.Hosting": "1.2.0-*", - "Microsoft.AspNetCore.Rewrite": "1.1.0-*", + "Microsoft.AspNetCore.Rewrite": "1.2.0-*", "Microsoft.AspNetCore.TestHost": "1.2.0-*", "Microsoft.Extensions.Logging.Testing": "1.2.0-*", "xunit": "2.2.0-*" From 883a1b2106c9a67def0f92fe17d238e430978538 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Tue, 10 Jan 2017 13:54:25 -0600 Subject: [PATCH 11/19] revert version number change per code review feedback --- samples/RewriteSample/project.json | 2 +- src/Microsoft.AspNetCore.Rewrite/project.json | 2 +- test/Microsoft.AspNetCore.Rewrite.Tests/project.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/RewriteSample/project.json b/samples/RewriteSample/project.json index 5066193e..fdfd4c81 100644 --- a/samples/RewriteSample/project.json +++ b/samples/RewriteSample/project.json @@ -1,6 +1,6 @@ { "dependencies": { - "Microsoft.AspNetCore.Rewrite": "1.2.0-*", + "Microsoft.AspNetCore.Rewrite": "1.1.0-*", "Microsoft.AspNetCore.Server.Kestrel": "1.2.0-*", "Microsoft.AspNetCore.Server.Kestrel.Https": "1.2.0-*" }, diff --git a/src/Microsoft.AspNetCore.Rewrite/project.json b/src/Microsoft.AspNetCore.Rewrite/project.json index ede20b49..0b9bb3a5 100644 --- a/src/Microsoft.AspNetCore.Rewrite/project.json +++ b/src/Microsoft.AspNetCore.Rewrite/project.json @@ -1,5 +1,5 @@ { - "version": "1.2.0-*", + "version": "1.1.0-*", "buildOptions": { "warningsAsErrors": true, "keyFile": "../../tools/Key.snk", diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/project.json b/test/Microsoft.AspNetCore.Rewrite.Tests/project.json index 9368d9a4..16c8c03f 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/project.json +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/project.json @@ -6,7 +6,7 @@ "dependencies": { "dotnet-test-xunit": "2.2.0-*", "Microsoft.AspNetCore.Hosting": "1.2.0-*", - "Microsoft.AspNetCore.Rewrite": "1.2.0-*", + "Microsoft.AspNetCore.Rewrite": "1.1.0-*", "Microsoft.AspNetCore.TestHost": "1.2.0-*", "Microsoft.Extensions.Logging.Testing": "1.2.0-*", "xunit": "2.2.0-*" From a88996d304012ce6084c98030f83944acd509b89 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Wed, 11 Jan 2017 11:36:14 -0600 Subject: [PATCH 12/19] add extension method and initial failing tests --- .../IISUrlRewriteOptionsExtensions.cs | 12 ++- .../IISUrlRewrite/MiddleWareTests.cs | 75 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs index bed837ae..1d1b85aa 100644 --- a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs @@ -65,5 +65,15 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextR return options; } + + /// + /// Add an IIS rewrite map + /// + /// The + /// The rewrite map + public static RewriteOptions AddIISRewriteMap(this RewriteOptions options, IISRewriteMap map) + { + return options; + } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs index b2724e8d..5c26559a 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs @@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.TestHost; using Microsoft.Net.Http.Headers; using Xunit; @@ -451,5 +453,76 @@ public async Task ThrowIndexOutOfRangeExceptionWithCorrectMessage() Assert.Equal("Cannot access back reference at index 9. Only 5 back references were captured.", ex.Message); } + + [Theory] + [InlineData("http://fetch.environment.local/dev/path", "http://1.1.1.1/path")] + [InlineData("http://fetch.environment.local/qa/path", "http://fetch.environment.local/qa/path")] + public async Task Invoke_ReverseProxyToAnotherSiteUsingXmlConfiguredRewriteMap(string requestUri, string expectedRewrittenUri) + { + var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" + + + + + + + + + + + + + + + + ")); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetStringAsync(new Uri(requestUri)); + + Assert.Equal(expectedRewrittenUri, response); + } + + [Theory] + [InlineData("http://fetch.environment.local/dev/path", "http://1.1.1.1/path")] + [InlineData("http://fetch.environment.local/qa/path", "http://fetch.environment.local/qa/path")] + public async Task Invoke_ReverseProxyToAnotherSiteUsingProgrammaticallyConfiguredRewriteMap(string requestUri, string expectedRewrittenUri) + { + var map = new IISRewriteMap("environmentMap") + { + ["dev"] = "1.1.1.1" + }; + + var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" + + + + + + + + + + + ")); + options.AddIISRewriteMap(map); + var builder = new WebHostBuilder() + .Configure(app => + { + app.UseRewriter(options); + app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); + }); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetStringAsync(new Uri(requestUri)); + + Assert.Equal(expectedRewrittenUri, response); + } } -} +} \ No newline at end of file From 2ccb4624e22201e4278b2bfcf2211a169cfc58b2 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Fri, 13 Jan 2017 14:13:00 -0600 Subject: [PATCH 13/19] add support for adding externally generated rewrite maps --- .../IISUrlRewriteOptionsExtensions.cs | 45 +++++++++---------- .../IISUrlRewrite/UrlRewriteFileParser.cs | 34 +++++++++++++- .../IISUrlRewrite/MiddleWareTests.cs | 7 ++- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs index 1d1b85aa..92e01bf9 100644 --- a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs @@ -2,6 +2,7 @@ // 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.Internal.IISUrlRewrite; using Microsoft.Extensions.FileProviders; @@ -13,13 +14,15 @@ namespace Microsoft.AspNetCore.Rewrite /// public static class IISUrlRewriteOptionsExtensions { - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The - /// The path to the file containing UrlRewrite rules. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath) + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The + /// The path to the file containing UrlRewrite rules. + /// An optional set of rewrite maps to uztilize when parsing rules. + /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath, IEnumerable rewriteMaps = null) { if (options == null) { @@ -35,16 +38,18 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFile using (var stream = file.CreateReadStream()) { - return AddIISUrlRewrite(options, new StreamReader(stream)); + return AddIISUrlRewrite(options, new StreamReader(stream), rewriteMaps); } } - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The text reader stream. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader) + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The text reader stream. + /// An optional set of rewrite maps to uztilize when parsing rules. + /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader, IEnumerable rewriteMaps = null) { if (options == null) { @@ -56,7 +61,7 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextR throw new ArgumentException(nameof(reader)); } - var rules = new UrlRewriteFileParser().Parse(reader); + var rules = new UrlRewriteFileParser().Parse(reader, rewriteMaps); foreach (var rule in rules) { @@ -65,15 +70,5 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextR return options; } - - /// - /// Add an IIS rewrite map - /// - /// The - /// The rewrite map - public static RewriteOptions AddIISRewriteMap(this RewriteOptions options, IISRewriteMap map) - { - return options; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index caecd64d..df40ce92 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -14,14 +14,20 @@ public class UrlRewriteFileParser { private InputParser _inputParser; - public IList Parse(TextReader reader) + /// + /// Parse an IIS rewrite section into a list of s. + /// + /// The reader containing the rewrite XML + /// An optional set of rewrite maps to uztilize when parsing rules. + /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. + public IList Parse(TextReader reader, IEnumerable rewriteMaps = null) { var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); if (xmlRoot != null) { - _inputParser = new InputParser(RewriteMapParser.Parse(xmlRoot)); + _inputParser = new InputParser(SetUpRewriteMaps(xmlRoot, rewriteMaps)); var result = new List(); // TODO Global rules are currently not treated differently than normal rules, fix. @@ -33,6 +39,30 @@ public IList Parse(TextReader reader) return null; } + private static IDictionary SetUpRewriteMaps(XElement xmlRoot, IEnumerable rewriteMaps) + { + if (xmlRoot == null && rewriteMaps == null) + { + return null; + } + + var iisRewriteMaps = RewriteMapParser.Parse(xmlRoot); + + if (rewriteMaps != null) + { + if (iisRewriteMaps == null) + { + iisRewriteMaps = new Dictionary(); + } + foreach (var rewriteMap in rewriteMaps) + { + iisRewriteMaps[rewriteMap.Name] = rewriteMap; + } + } + + return iisRewriteMaps; + } + private void ParseRules(XElement rules, IList result) { if (rules == null) diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs index 5c26559a..508c53c6 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs @@ -498,8 +498,7 @@ public async Task Invoke_ReverseProxyToAnotherSiteUsingProgrammaticallyConfigure { ["dev"] = "1.1.1.1" }; - - var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(@" + const string xml = @" @@ -510,8 +509,8 @@ public async Task Invoke_ReverseProxyToAnotherSiteUsingProgrammaticallyConfigure - ")); - options.AddIISRewriteMap(map); + "; + var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(xml), new []{map}); var builder = new WebHostBuilder() .Configure(app => { From 7a8ac6b922f370b24b673c36c89e09a31395190b Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Thu, 19 Jan 2017 14:22:54 -0600 Subject: [PATCH 14/19] add IISRewriteMapCollection --- .../Internal/IISUrlRewrite/IISRewriteMap.cs | 5 ++- .../IISUrlRewrite/IISRewriteMapCollection.cs | 42 +++++++++++++++++++ .../Internal/IISUrlRewrite/InputParser.cs | 8 ++-- .../IISUrlRewrite/RewriteMapParser.cs | 9 ++-- .../IISUrlRewrite/UrlRewriteFileParser.cs | 6 +-- .../IISUrlRewrite/InputParserTests.cs | 4 +- .../IISUrlRewrite/RewriteMapParserTests.cs | 3 +- 7 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMapCollection.cs diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs index 75440929..0cd52338 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMap.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMapCollection.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMapCollection.cs new file mode 100644 index 00000000..4f8a9006 --- /dev/null +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/IISRewriteMapCollection.cs @@ -0,0 +1,42 @@ +// 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; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite +{ + public class IISRewriteMapCollection : IEnumerable + { + private readonly Dictionary _rewriteMaps = new Dictionary(); + + public void Add(IISRewriteMap rewriteMap) + { + if (rewriteMap != null) + { + _rewriteMaps[rewriteMap.Name] = rewriteMap; + } + } + + public int Count => _rewriteMaps.Count; + + public IISRewriteMap this[string key] + { + get + { + IISRewriteMap value; + return _rewriteMaps.TryGetValue(key, out value) ? value : null; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _rewriteMaps.Values.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return _rewriteMaps.Values.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index 984fcacc..e4af7deb 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -12,13 +12,13 @@ public class InputParser private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; - private readonly IDictionary _rewriteMaps; + private readonly IISRewriteMapCollection _rewriteMaps; public InputParser() { } - public InputParser(IDictionary rewriteMaps) + public InputParser(IISRewriteMapCollection rewriteMaps) { _rewriteMaps = rewriteMaps; } @@ -137,8 +137,8 @@ private void ParseParameter(ParserContext context, IList results return; } default: - IISRewriteMap rewriteMap; - if (_rewriteMaps != null && _rewriteMaps.TryGetValue(parameter, out rewriteMap)) + IISRewriteMap rewriteMap = _rewriteMaps?[parameter]; + if (rewriteMap != null) { var pattern = ParseString(context); results.Add(new RewriteMapSegment(rewriteMap, pattern)); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 436720fe..2b9a000a 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -7,7 +6,7 @@ namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public static class RewriteMapParser { - public static IDictionary Parse(XElement xmlRoot) + public static IISRewriteMapCollection Parse(XElement xmlRoot) { if (xmlRoot == null) { @@ -20,15 +19,15 @@ public static IDictionary Parse(XElement xmlRoot) return null; } - var rewriteMaps = new Dictionary(); + var rewriteMaps = new IISRewriteMapCollection(); foreach (var mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) { var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); foreach (var addElement in mapElement.Elements(RewriteTags.Add)) { - map[addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant()] = addElement.Attribute(RewriteTags.Value).Value; + map[addElement.Attribute(RewriteTags.Key).Value.ToLowerInvariant()] = addElement.Attribute(RewriteTags.Value).Value; } - rewriteMaps.Add(map.Name, map); + rewriteMaps.Add(map); } return rewriteMaps; diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index df40ce92..be74ddf6 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -39,7 +39,7 @@ public IList Parse(TextReader reader, IEnumerable SetUpRewriteMaps(XElement xmlRoot, IEnumerable rewriteMaps) + private static IISRewriteMapCollection SetUpRewriteMaps(XElement xmlRoot, IEnumerable rewriteMaps) { if (xmlRoot == null && rewriteMaps == null) { @@ -52,11 +52,11 @@ private static IDictionary SetUpRewriteMaps(XElement xmlR { if (iisRewriteMaps == null) { - iisRewriteMaps = new Dictionary(); + iisRewriteMaps = new IISRewriteMapCollection(); } foreach (var rewriteMap in rewriteMaps) { - iisRewriteMaps[rewriteMap.Name] = rewriteMap; + iisRewriteMaps.Add(rewriteMap); } } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index 5a893d4d..e1689e55 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -105,7 +105,7 @@ public void Should_throw_FormatException_if_rewrite_map_not_found() const string definedMapName = "testMap"; const string undefinedMapName = "apiMap"; var map = new IISRewriteMap(definedMapName); - var maps = new Dictionary { { map.Name, map } }; + var maps = new IISRewriteMapCollection { map }; Assert.Throws(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}")); } @@ -117,7 +117,7 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() const string expectedValue = "test.com/api"; var map = new IISRewriteMap(expectedMapName); map[expectedKey] = expectedValue; - var maps = new Dictionary { { map.Name, map } }; + var maps = new IISRewriteMapCollection { map }; string inputString = $"{{{expectedMapName}:{{R:1}}}}"; Pattern pattern = new InputParser(maps).ParseInputString(inputString); diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs index 8a86123c..4bcccf08 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs @@ -34,8 +34,7 @@ public void Should_parse_rewrite_map() // assert Assert.Equal(1, actualMaps.Count); - IISRewriteMap actualMap; - Assert.True(actualMaps.TryGetValue(expectedMapName, out actualMap)); + IISRewriteMap actualMap = actualMaps[expectedMapName]; Assert.NotNull(actualMap); Assert.Equal(expectedMapName, actualMap.Name); Assert.Equal(expectedValue, actualMap[expectedKey]); From 9eb1b74a7c6b1af9bd5313a5e27b5867e54f175d Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 30 Jan 2017 12:05:26 -0600 Subject: [PATCH 15/19] code review feedback --- .../Internal/IISUrlRewrite/InputParser.cs | 2 +- .../Internal/IISUrlRewrite/RewriteMapParser.cs | 5 ++++- .../Internal/PatternSegments/RewriteMapSegment.cs | 5 ++++- .../IISUrlRewrite/InputParserTests.cs | 8 +++----- .../IISUrlRewrite/RewriteMapParserTests.cs | 8 ++++---- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index e4af7deb..92a80184 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -137,7 +137,7 @@ private void ParseParameter(ParserContext context, IList results return; } default: - IISRewriteMap rewriteMap = _rewriteMaps?[parameter]; + var rewriteMap = _rewriteMaps?[parameter]; if (rewriteMap != null) { var pattern = ParseString(context); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs index 2b9a000a..4e9b21d8 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/RewriteMapParser.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Linq; using System.Xml.Linq; @@ -10,7 +13,7 @@ public static IISRewriteMapCollection Parse(XElement xmlRoot) { if (xmlRoot == null) { - throw new ArgumentException(nameof(xmlRoot)); + throw new ArgumentNullException(nameof(xmlRoot)); } var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs index ea0def1a..3dd50c9f 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/PatternSegments/RewriteMapSegment.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; namespace Microsoft.AspNetCore.Rewrite.Internal.PatternSegments { diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index e1689e55..3c914c32 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; @@ -10,7 +9,6 @@ using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; using Microsoft.Extensions.Logging.Testing; - using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite @@ -119,11 +117,11 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() map[expectedKey] = expectedValue; var maps = new IISRewriteMapCollection { map }; - string inputString = $"{{{expectedMapName}:{{R:1}}}}"; - Pattern pattern = new InputParser(maps).ParseInputString(inputString); + var inputString = $"{{{expectedMapName}:{{R:1}}}}"; + var pattern = new InputParser(maps).ParseInputString(inputString); Assert.Equal(1, pattern.PatternSegments.Count); - PatternSegment segment = pattern.PatternSegments.Single(); + var segment = pattern.PatternSegments.Single(); var rewriteMapSegment = segment as RewriteMapSegment; Assert.NotNull(rewriteMapSegment); diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs index 4bcccf08..a917e675 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/RewriteMapParserTests.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +// 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.Linq; using System.Xml.Linq; - using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; - using Xunit; namespace Microsoft.AspNetCore.Rewrite.Tests.IISUrlRewrite @@ -34,7 +34,7 @@ public void Should_parse_rewrite_map() // assert Assert.Equal(1, actualMaps.Count); - IISRewriteMap actualMap = actualMaps[expectedMapName]; + var actualMap = actualMaps[expectedMapName]; Assert.NotNull(actualMap); Assert.Equal(expectedMapName, actualMap.Name); Assert.Equal(expectedValue, actualMap[expectedKey]); From 43fbf3b697ddc1a4962c2cb5c2b3168648136017 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Mon, 30 Jan 2017 12:13:42 -0600 Subject: [PATCH 16/19] add overloads to eliminate optional parameters per ms code standards --- .../IISUrlRewriteOptionsExtensions.cs | 55 +++++++++++++------ .../IISUrlRewrite/UrlRewriteFileParser.cs | 34 ++++++++---- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs index 92e01bf9..c484d8fb 100644 --- a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs @@ -14,15 +14,26 @@ namespace Microsoft.AspNetCore.Rewrite /// public static class IISUrlRewriteOptionsExtensions { - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The - /// The path to the file containing UrlRewrite rules. - /// An optional set of rewrite maps to uztilize when parsing rules. - /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath, IEnumerable rewriteMaps = null) + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The + /// The path to the file containing UrlRewrite rules. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath) + { + return AddIISUrlRewrite(options, fileProvider, filePath, null); + } + + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The + /// The path to the file containing UrlRewrite rules. + /// A set of rewrite maps to uztilize when parsing rules. + /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath, IEnumerable rewriteMaps) { if (options == null) { @@ -42,14 +53,24 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFile } } - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The text reader stream. - /// An optional set of rewrite maps to uztilize when parsing rules. - /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader, IEnumerable rewriteMaps = null) + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The text reader stream. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader) + { + return AddIISUrlRewrite(options, reader, null); + } + + /// + /// Add rules from a IIS config file containing Url Rewrite rules + /// + /// The + /// The text reader stream. + /// A set of rewrite maps to uztilize when parsing rules. + /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader, IEnumerable rewriteMaps) { if (options == null) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index be74ddf6..968517d8 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -18,25 +18,35 @@ public class UrlRewriteFileParser /// Parse an IIS rewrite section into a list of s. /// /// The reader containing the rewrite XML - /// An optional set of rewrite maps to uztilize when parsing rules. + public IList Parse(TextReader reader) + { + return Parse(reader, null); + } + + /// + /// Parse an IIS rewrite section into a list of s. + /// + /// The reader containing the rewrite XML + /// A set of rewrite maps to uztilize when parsing rules. /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public IList Parse(TextReader reader, IEnumerable rewriteMaps = null) + public IList Parse(TextReader reader, IEnumerable rewriteMaps) { var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); - if (xmlRoot != null) + if (xmlRoot == null) { - _inputParser = new InputParser(SetUpRewriteMaps(xmlRoot, rewriteMaps)); - - var result = new List(); - // TODO Global rules are currently not treated differently than normal rules, fix. - // See: https://github.com/aspnet/BasicMiddleware/issues/59 - ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result); - ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result); - return result; + return null; } - return null; + + _inputParser = new InputParser(SetUpRewriteMaps(xmlRoot, rewriteMaps)); + + var result = new List(); + // TODO Global rules are currently not treated differently than normal rules, fix. + // See: https://github.com/aspnet/BasicMiddleware/issues/59 + ParseRules(xmlRoot.Descendants(RewriteTags.GlobalRules).FirstOrDefault(), result); + ParseRules(xmlRoot.Descendants(RewriteTags.Rules).FirstOrDefault(), result); + return result; } private static IISRewriteMapCollection SetUpRewriteMaps(XElement xmlRoot, IEnumerable rewriteMaps) From 7d1cc11adb910b6ebaaa7c6c6917c2416ed0296e Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Wed, 1 Feb 2017 11:13:32 -0600 Subject: [PATCH 17/19] remove extra ability to layer in external rewrite maps per code review feedback --- .../IISUrlRewriteOptionsExtensions.cs | 31 ++-------------- .../IISUrlRewrite/UrlRewriteFileParser.cs | 37 +------------------ .../IISUrlRewrite/MiddleWareTests.cs | 35 ------------------ 3 files changed, 4 insertions(+), 99 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs index c484d8fb..ff547952 100644 --- a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs @@ -2,7 +2,6 @@ // 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.Internal.IISUrlRewrite; using Microsoft.Extensions.FileProviders; @@ -21,19 +20,6 @@ public static class IISUrlRewriteOptionsExtensions /// The /// The path to the file containing UrlRewrite rules. public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath) - { - return AddIISUrlRewrite(options, fileProvider, filePath, null); - } - - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The - /// The path to the file containing UrlRewrite rules. - /// A set of rewrite maps to uztilize when parsing rules. - /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFileProvider fileProvider, string filePath, IEnumerable rewriteMaps) { if (options == null) { @@ -49,28 +35,17 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFile using (var stream = file.CreateReadStream()) { - return AddIISUrlRewrite(options, new StreamReader(stream), rewriteMaps); + return AddIISUrlRewrite(options, new StreamReader(stream)); } } - /// - /// Add rules from a IIS config file containing Url Rewrite rules - /// - /// The - /// The text reader stream. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader) - { - return AddIISUrlRewrite(options, reader, null); - } /// /// Add rules from a IIS config file containing Url Rewrite rules /// /// The /// The text reader stream. - /// A set of rewrite maps to uztilize when parsing rules. - /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader, IEnumerable rewriteMaps) + public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader) { if (options == null) { @@ -82,7 +57,7 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextR throw new ArgumentException(nameof(reader)); } - var rules = new UrlRewriteFileParser().Parse(reader, rewriteMaps); + var rules = new UrlRewriteFileParser().Parse(reader); foreach (var rule in rules) { diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs index 968517d8..ed6b0255 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs @@ -19,17 +19,6 @@ public class UrlRewriteFileParser /// /// The reader containing the rewrite XML public IList Parse(TextReader reader) - { - return Parse(reader, null); - } - - /// - /// Parse an IIS rewrite section into a list of s. - /// - /// The reader containing the rewrite XML - /// A set of rewrite maps to uztilize when parsing rules. - /// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML. - public IList Parse(TextReader reader, IEnumerable rewriteMaps) { var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo); var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault(); @@ -39,7 +28,7 @@ public IList Parse(TextReader reader, IEnumerable(); // TODO Global rules are currently not treated differently than normal rules, fix. @@ -49,30 +38,6 @@ public IList Parse(TextReader reader, IEnumerable rewriteMaps) - { - if (xmlRoot == null && rewriteMaps == null) - { - return null; - } - - var iisRewriteMaps = RewriteMapParser.Parse(xmlRoot); - - if (rewriteMaps != null) - { - if (iisRewriteMaps == null) - { - iisRewriteMaps = new IISRewriteMapCollection(); - } - foreach (var rewriteMap in rewriteMaps) - { - iisRewriteMaps.Add(rewriteMap); - } - } - - return iisRewriteMaps; - } - private void ParseRules(XElement rules, IList result) { if (rules == null) diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs index 508c53c6..abd3ed77 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/MiddleWareTests.cs @@ -488,40 +488,5 @@ public async Task Invoke_ReverseProxyToAnotherSiteUsingXmlConfiguredRewriteMap(s Assert.Equal(expectedRewrittenUri, response); } - - [Theory] - [InlineData("http://fetch.environment.local/dev/path", "http://1.1.1.1/path")] - [InlineData("http://fetch.environment.local/qa/path", "http://fetch.environment.local/qa/path")] - public async Task Invoke_ReverseProxyToAnotherSiteUsingProgrammaticallyConfiguredRewriteMap(string requestUri, string expectedRewrittenUri) - { - var map = new IISRewriteMap("environmentMap") - { - ["dev"] = "1.1.1.1" - }; - const string xml = @" - - - - - - - - - - - "; - var options = new RewriteOptions().AddIISUrlRewrite(new StringReader(xml), new []{map}); - var builder = new WebHostBuilder() - .Configure(app => - { - app.UseRewriter(options); - app.Run(context => context.Response.WriteAsync(context.Request.GetEncodedUrl())); - }); - var server = new TestServer(builder); - - var response = await server.CreateClient().GetStringAsync(new Uri(requestUri)); - - Assert.Equal(expectedRewrittenUri, response); - } } } \ No newline at end of file From 461d587ce5862eb9d0dfc54b91c3d1d5d6b5c8d6 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Thu, 2 Feb 2017 10:44:19 -0600 Subject: [PATCH 18/19] fix whitespace --- .../IISUrlRewriteOptionsExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs index ff547952..e31819b1 100644 --- a/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs +++ b/src/Microsoft.AspNetCore.Rewrite/IISUrlRewriteOptionsExtensions.cs @@ -39,7 +39,6 @@ public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, IFile } } - /// /// Add rules from a IIS config file containing Url Rewrite rules /// From 435fed7832803cad33afd67495a2af4c6a2d2a35 Mon Sep 17 00:00:00 2001 From: david-peden-q2 Date: Tue, 7 Feb 2017 14:11:16 -0600 Subject: [PATCH 19/19] fix breakages after merge --- .../Internal/IISUrlRewrite/InputParser.cs | 2 +- .../IISUrlRewrite/InputParserTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs index f1cd00ce..d73e561d 100644 --- a/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs +++ b/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/InputParser.cs @@ -141,7 +141,7 @@ private void ParseParameter(ParserContext context, IList results var rewriteMap = _rewriteMaps?[parameter]; if (rewriteMap != null) { - var pattern = ParseString(context); + var pattern = ParseString(context, global); results.Add(new RewriteMapSegment(rewriteMap, pattern)); return; } diff --git a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs index f0855eaf..d636f152 100644 --- a/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs +++ b/test/Microsoft.AspNetCore.Rewrite.Tests/IISUrlRewrite/InputParserTests.cs @@ -94,7 +94,7 @@ public void FormatExceptionsOnBadSyntax(string testString) [Fact] public void Should_throw_FormatException_if_no_rewrite_maps_are_defined() { - Assert.Throws(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}")); + Assert.Throws(() => new InputParser(null).ParseInputString("{apiMap:{R:1}}", global: false)); } [Fact] @@ -104,7 +104,7 @@ public void Should_throw_FormatException_if_rewrite_map_not_found() const string undefinedMapName = "apiMap"; var map = new IISRewriteMap(definedMapName); var maps = new IISRewriteMapCollection { map }; - Assert.Throws(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}")); + Assert.Throws(() => new InputParser(maps).ParseInputString($"{{{undefinedMapName}:{{R:1}}}}", global: false)); } [Fact] @@ -118,7 +118,7 @@ public void Should_parse_RewriteMapSegment_and_successfully_evaluate_result() var maps = new IISRewriteMapCollection { map }; var inputString = $"{{{expectedMapName}:{{R:1}}}}"; - var pattern = new InputParser(maps).ParseInputString(inputString); + var pattern = new InputParser(maps).ParseInputString(inputString, global: false); Assert.Equal(1, pattern.PatternSegments.Count); var segment = pattern.PatternSegments.Single();