| File: ApacheModRewrite\TestStringParser.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.ApacheModRewrite; /// <summary> /// Parses the TestString segment of the mod_rewrite condition. /// </summary> internal sealed class TestStringParser { private const char Percent = '%'; private const char Dollar = '$'; private const char Colon = ':'; private const char OpenBrace = '{'; private const char CloseBrace = '}'; /// <summary> /// Creates a pattern, which is a template to create a new test string to /// compare to the condition pattern. Can contain server variables, back references, etc. /// </summary> /// <param name="testString">The test string portion of the RewriteCond /// Examples: /// %{REMOTE_ADDR} /// /var/www/%{REQUEST_URI} /// %1 /// $1</param> /// <returns>A new <see cref="Pattern"/>, containing a list of <see cref="PatternSegment"/></returns> /// http://httpd.apache.org/docs/current/mod/mod_rewrite.html public static Pattern Parse(string testString) { if (testString == null) { testString = string.Empty; } var context = new ParserContext(testString); var results = new List<PatternSegment>(); while (context.Next()) { switch (context.Current) { case Percent: // This is a server parameter, parse for a condition variable if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserUnrecognizedParameter(testString, context.Index)); } ParseConditionParameter(context, results); break; case Dollar: // This is a parameter from the rule, verify that it is a number from 0 to 9 directly after it // and create a new Pattern Segment. if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserNoBackreference(context.Index)); } context.Mark(); if (context.Current >= '0' && context.Current <= '9') { context.Next(); var ruleVariable = context.Capture()!; context.Back(); var parsedIndex = int.Parse(ruleVariable, CultureInfo.InvariantCulture); results.Add(new RuleMatchSegment(parsedIndex)); } else { throw new FormatException(Resources.FormatError_InputParserInvalidInteger(testString, context.Index)); } break; default: ParseLiteral(context, results); break; } } return new Pattern(results); } /// <summary> /// Obtains the condition parameter, which could either be a condition variable or a /// server variable. Assumes the current character is immediately after the '%'. /// context, on return will be on the last character of variable captured, such that after /// Next() is called, it will be on the character immediately after the condition parameter. /// </summary> /// <param name="context">The ParserContext</param> /// <param name="results">The List of results which the new condition parameter will be added.</param> /// <returns>true </returns> private static void ParseConditionParameter(ParserContext context, IList<PatternSegment> results) { // Parse { } if (context.Current == OpenBrace) { // Start of a server variable if (!context.Next()) { // Dangling { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } context.Mark(); while (context.Current != CloseBrace) { if (!context.Next()) { throw new FormatException(Resources.FormatError_InputParserMissingCloseBrace(context.Index)); } else if (context.Current == Colon) { // Have a segmented look up Ex: HTTP:xxxx // Most of these we can't handle throw new NotImplementedException("Segmented Lookups no implemented"); } } // Need to verify server variable captured exists var rawServerVariable = context.Capture()!; results.Add(ServerVariables.FindServerVariable(rawServerVariable, context)); } else if (context.Current >= '0' && context.Current <= '9') { // means we have a segmented lookup // store information in the testString result to know what to look up. context.Mark(); context.Next(); var rawConditionParameter = context.Capture()!; // Once we leave this method, the while loop will call next again. Because // capture is exclusive, we need to go one past the end index, capture, and then go back. context.Back(); var parsedIndex = int.Parse(rawConditionParameter, CultureInfo.InvariantCulture); results.Add(new ConditionMatchSegment(parsedIndex)); } else { // illegal escape of a character throw new FormatException(Resources.FormatError_InputParserInvalidInteger(context.Template, context.Index)); } } /// <summary> /// Parse a string literal in the test string. Continues capturing until the start of a new variable type. /// </summary> /// <param name="context"></param> /// <param name="results"></param> /// <returns></returns> private static void ParseLiteral(ParserContext context, IList<PatternSegment> results) { context.Mark(); string? literal; while (true) { if (context.Current == Percent || context.Current == Dollar) { literal = context.Capture(); context.Back(); break; } if (!context.Next()) { literal = context.Capture(); break; } } // add results results.Add(new LiteralSegment(literal!)); } }