| File: IISUrlRewrite\InputParser.cs | Web Access |
| Project: src\aspnetcore\src\Middleware\Rewrite\src\Microsoft.AspNetCore.Rewrite.csproj (Microsoft.AspNetCore.Rewrite) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; using Microsoft.AspNetCore.Rewrite.PatternSegments; namespace Microsoft.AspNetCore.Rewrite.IISUrlRewrite; internal sealed class InputParser { private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; private readonly IISRewriteMapCollection? _rewriteMaps; private readonly bool _alwaysUseManagedServerVariables; public InputParser() { } public InputParser(IISRewriteMapCollection? rewriteMaps, bool alwaysUseManagedServerVariables) { _rewriteMaps = rewriteMaps; _alwaysUseManagedServerVariables = alwaysUseManagedServerVariables; } /// <summary> /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// </summary> /// <param name="testString"></param> /// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns> public Pattern ParseInputString(string testString) { return ParseInputString(testString, UriMatchPart.Path); } /// <summary> /// Creates a pattern, which is a template to create a new test string to /// compare to the condition. Can contain server variables, back references, etc. /// </summary> /// <param name="testString"></param> /// <param name="uriMatchPart">When testString evaluates to a URL segment, specify which part of the URI to evaluate.</param> /// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns> 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<PatternSegment>(); 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<PatternSegment> 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, _alwaysUseManagedServerVariables)); 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": { var pattern = ParseString(context, uriMatchPart); results.Add(new UrlDecodeSegment(pattern)); if (context.Current != CloseBrace) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } return; } 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<PatternSegment> results) { context.Mark(); string? literal; while (true) { if (context.Current == OpenBrace || context.Current == CloseBrace) { literal = context.Capture(); context.Back(); break; } if (!context.Next()) { literal = context.Capture(); break; } } results.Add(new LiteralSegment(literal!)); } }