aspnetcore/src/Microsoft.AspNetCore.Rewrite/Internal/IISUrlRewrite/UrlRewriteFileParser.cs

228 lines
9.0 KiB
C#

// 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.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite
{
public class UrlRewriteFileParser
{
private readonly InputParser _inputParser = new InputParser();
public IList<IISUrlRewriteRule> Parse(TextReader reader)
{
var xmlDoc = XDocument.Load(reader, LoadOptions.SetLineInfo);
var xmlRoot = xmlDoc.Descendants(RewriteTags.Rewrite).FirstOrDefault();
if (xmlRoot != null)
{
var result = new List<IISUrlRewriteRule>();
// 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;
}
private void ParseRules(XElement rules, IList<IISUrlRewriteRule> result)
{
if (rules == null)
{
return;
}
if (string.Equals(rules.Name.ToString(), "GlobalRules", StringComparison.OrdinalIgnoreCase))
{
throw new NotSupportedException("Support for global rules has not been implemented yet");
}
foreach (var rule in rules.Elements(RewriteTags.Rule))
{
var builder = new UrlRewriteRuleBuilder();
ParseRuleAttributes(rule, builder);
if (builder.Enabled)
{
result.Add(builder.Build());
}
}
}
private void ParseRuleAttributes(XElement rule, UrlRewriteRuleBuilder builder)
{
builder.Name = rule.Attribute(RewriteTags.Name)?.Value;
if (ParseBool(rule, RewriteTags.Enabled, defaultValue: true))
{
builder.Enabled = true;
}
else
{
return;
}
var patternSyntax = ParseEnum(rule, RewriteTags.PatternSyntax, PatternSyntax.ECMAScript);
var stopProcessing = ParseBool(rule, RewriteTags.StopProcessing, defaultValue: false);
var match = rule.Element(RewriteTags.Match);
if (match == null)
{
ThrowUrlFormatException(rule, "Cannot have rule without match");
}
var action = rule.Element(RewriteTags.Action);
if (action == null)
{
ThrowUrlFormatException(rule, "Rule does not have an associated action attribute");
}
ParseMatch(match, builder, patternSyntax);
ParseConditions(rule.Element(RewriteTags.Conditions), builder, patternSyntax);
ParseUrlAction(action, builder, stopProcessing);
}
private void ParseMatch(XElement match, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
{
var parsedInputString = match.Attribute(RewriteTags.Url)?.Value;
if (parsedInputString == null)
{
ThrowUrlFormatException(match, "Match must have Url Attribute");
}
var ignoreCase = ParseBool(match, RewriteTags.IgnoreCase, defaultValue: true);
var negate = ParseBool(match, RewriteTags.Negate, defaultValue: false);
builder.AddUrlMatch(parsedInputString, ignoreCase, negate, patternSyntax);
}
private void ParseConditions(XElement conditions, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax)
{
if (conditions == null)
{
return;
}
var grouping = ParseEnum(conditions, RewriteTags.LogicalGrouping, LogicalGrouping.MatchAll);
var trackAllCaptures = ParseBool(conditions, RewriteTags.TrackAllCaptures, defaultValue: false);
builder.AddUrlConditions(grouping, trackAllCaptures);
foreach (var cond in conditions.Elements(RewriteTags.Add))
{
ParseCondition(cond, builder, patternSyntax, trackAllCaptures);
}
}
private void ParseCondition(XElement condition, UrlRewriteRuleBuilder builder, PatternSyntax patternSyntax, bool trackAllCaptures)
{
var ignoreCase = ParseBool(condition, RewriteTags.IgnoreCase, defaultValue: true);
var negate = ParseBool(condition, RewriteTags.Negate, defaultValue: false);
var matchType = ParseEnum(condition, RewriteTags.MatchType, MatchType.Pattern);
var parsedInputString = condition.Attribute(RewriteTags.Input)?.Value;
if (parsedInputString == null)
{
ThrowUrlFormatException(condition, "Conditions must have an input attribute");
}
var parsedPatternString = condition.Attribute(RewriteTags.Pattern)?.Value;
try
{
var input = _inputParser.ParseInputString(parsedInputString);
builder.AddUrlCondition(input, parsedPatternString, patternSyntax, matchType, ignoreCase, negate, trackAllCaptures);
}
catch (FormatException formatException)
{
ThrowUrlFormatException(condition, formatException.Message, formatException);
}
}
private void ParseUrlAction(XElement urlAction, UrlRewriteRuleBuilder builder, bool stopProcessing)
{
var actionType = ParseEnum(urlAction, RewriteTags.Type, ActionType.None);
var redirectType = ParseEnum(urlAction, RewriteTags.RedirectType, RedirectType.Permanent);
var appendQuery = ParseBool(urlAction, RewriteTags.AppendQueryString, defaultValue: true);
string url = string.Empty;
if (urlAction.Attribute(RewriteTags.Url) != null)
{
url = urlAction.Attribute(RewriteTags.Url).Value;
if (string.IsNullOrEmpty(url))
{
ThrowUrlFormatException(urlAction, "Url attribute cannot contain an empty string");
}
}
try
{
var input = _inputParser.ParseInputString(url);
builder.AddUrlAction(input, actionType, appendQuery, stopProcessing, (int)redirectType);
}
catch (FormatException formatException)
{
ThrowUrlFormatException(urlAction, formatException.Message, formatException);
}
}
private static void ThrowUrlFormatException(XElement element, string message)
{
var lineInfo = (IXmlLineInfo)element;
var line = lineInfo.LineNumber;
var col = lineInfo.LinePosition;
throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col));
}
private static void ThrowUrlFormatException(XElement element, string message, Exception ex)
{
var lineInfo = (IXmlLineInfo)element;
var line = lineInfo.LineNumber;
var col = lineInfo.LinePosition;
throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col), ex);
}
private static void ThrowParameterFormatException(XElement element, string message)
{
var lineInfo = (IXmlLineInfo)element;
var line = lineInfo.LineNumber;
var col = lineInfo.LinePosition;
throw new FormatException(Resources.FormatError_UrlRewriteParseError(message, line, col));
}
private bool ParseBool(XElement element, string rewriteTag, bool defaultValue)
{
bool result;
var attribute = element.Attribute(rewriteTag);
if (attribute == null)
{
return defaultValue;
}
else if (!bool.TryParse(attribute.Value, out result))
{
ThrowParameterFormatException(element, $"The {rewriteTag} parameter '{attribute.Value}' was not recognized");
}
return result;
}
private TEnum ParseEnum<TEnum>(XElement element, string rewriteTag, TEnum defaultValue)
where TEnum : struct
{
TEnum enumResult = default(TEnum);
var attribute = element.Attribute(rewriteTag);
if (attribute == null)
{
return defaultValue;
}
else if(!Enum.TryParse(attribute.Value, ignoreCase: true, result: out enumResult))
{
ThrowParameterFormatException(element, $"The {rewriteTag} parameter '{attribute.Value}' was not recognized");
}
return enumResult;
}
}
}