// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite { public class InputParser { private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; private readonly IISRewriteMapCollection _rewriteMaps; public InputParser() { } public InputParser(IISRewriteMapCollection rewriteMaps) { _rewriteMaps = rewriteMaps; } /// /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// /// /// A new , containing a list of public Pattern ParseInputString(string testString) { return ParseInputString(testString, UriMatchPart.Path); } /// /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// /// /// When testString evaluates to a URL segment, specify which part of the URI to evaluate. /// A new , containing a list of public Pattern ParseInputString(string testString, UriMatchPart uriMatchPart) { if (testString == null) { testString = string.Empty; } var context = new ParserContext(testString); return ParseString(context, uriMatchPart); } private Pattern ParseString(ParserContext context, UriMatchPart uriMatchPart) { var results = new List(); while (context.Next()) { if (context.Current == OpenBrace) { // This is a server parameter, parse for a condition variable if (!context.Next()) { // missing { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } ParseParameter(context, results, uriMatchPart); } else if (context.Current == CloseBrace) { return new Pattern(results); } else { // Parse for literals, which will return on either the end of the test string // or when it hits a special character ParseLiteral(context, results); } } return new Pattern(results); } private void ParseParameter(ParserContext context, IList results, UriMatchPart uriMatchPart) { context.Mark(); // Four main cases: // 1. {NAME} - Server Variable, create lambda to get the part of the context // 2. {R:1} - IRule parameter // 3. {C:1} - Condition Parameter // 4. {function:xxx} - String function // (unless we support Reload) string parameter; while (context.Next()) { if (context.Current == CloseBrace) { // This is just a server variable, so we do a lookup and verify the server variable exists. parameter = context.Capture(); results.Add(ServerVariables.FindServerVariable(parameter, context, uriMatchPart)); return; } else if (context.Current == Colon) { parameter = context.Capture(); // Only 5 strings to expect here. Case sensitive. switch (parameter) { case "ToLower": { var pattern = ParseString(context, uriMatchPart); results.Add(new ToLowerSegment(pattern)); // at this point, we expect our context to be on the ending closing brace, // because the ParseString() call will increment the context until it // has processed the new string. if (context.Current != CloseBrace) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } return; } case "UrlDecode": { throw new NotImplementedException("UrlDecode is not implemented because of no great library available"); } case "UrlEncode": { var pattern = ParseString(context, uriMatchPart); results.Add(new UrlEncodeSegment(pattern)); if (context.Current != CloseBrace) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } return; } case "R": { var index = GetBackReferenceIndex(context); results.Add(new RuleMatchSegment(index)); return; } case "C": { var index = GetBackReferenceIndex(context); results.Add(new ConditionMatchSegment(index)); return; } default: var rewriteMap = _rewriteMaps?[parameter]; if (rewriteMap != null) { var pattern = ParseString(context, uriMatchPart); results.Add(new RewriteMapSegment(rewriteMap, pattern)); return; } throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(parameter, context.Index)); } } } throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } private static int GetBackReferenceIndex(ParserContext context) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); } context.Mark(); while (context.Current != CloseBrace) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } } var res = context.Capture(); int index; if (!int.TryParse(res, NumberStyles.None, CultureInfo.InvariantCulture, out index)) { throw new FormatException(Resources.FormatError_InputParserInvalidInteger(res, context.Index)); } if (index > 9 || index < 0) { throw new FormatException(Resources.FormatError_InputParserIndexOutOfRange(res, context.Index)); } return index; } private static void ParseLiteral(ParserContext context, IList results) { context.Mark(); string literal; while (true) { if (context.Current == OpenBrace || context.Current == CloseBrace) { literal = context.Capture(); context.Back(); break; } if (!context.Next()) { literal = context.Capture(); break; } } results.Add(new LiteralSegment(literal)); } } }