|
// 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!));
}
}
|