implement wildcard lenght quantifiers, TreatAsRegex property and solve catastrpohic backtracking (#2528)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Discord.Interactions
|
||||
{
|
||||
@@ -28,6 +29,11 @@ namespace Discord.Interactions
|
||||
/// </summary>
|
||||
public RunMode RunMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <see cref="CustomId"/> should be treated as a raw Regex pattern.
|
||||
/// </summary>
|
||||
public bool TreatAsRegex { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Create a command for component interaction handling.
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace Discord.Interactions
|
||||
/// </summary>
|
||||
public RunMode RunMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <see cref="CustomId"/> should be treated as a raw Regex pattern.
|
||||
/// </summary>
|
||||
public bool TreatAsRegex { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Create a command for modal interaction handling.
|
||||
/// </summary>
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace Discord.Interactions.Builders
|
||||
/// <inheritdoc/>
|
||||
public bool IgnoreGroupNames { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TreatNameAsRegex { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RunMode RunMode { get; set; }
|
||||
|
||||
@@ -117,6 +120,19 @@ namespace Discord.Interactions.Builders
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="TreatNameAsRegex"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">New value of the <see cref="TreatNameAsRegex"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithNameAsRegex (bool value)
|
||||
{
|
||||
TreatNameAsRegex = value;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds parameter builders to <see cref="Parameters"/>.
|
||||
/// </summary>
|
||||
@@ -163,6 +179,10 @@ namespace Discord.Interactions.Builders
|
||||
ICommandBuilder ICommandBuilder.SetRunMode (RunMode runMode) =>
|
||||
SetRunMode(runMode);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ICommandBuilder ICommandBuilder.WithNameAsRegex(bool value) =>
|
||||
WithNameAsRegex(value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ICommandBuilder ICommandBuilder.AddParameters (params IParameterBuilder[] parameters) =>
|
||||
AddParameters(parameters as TParamBuilder);
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
bool IgnoreGroupNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the <see cref="Name"/> should be directly used as a Regex pattern.
|
||||
/// </summary>
|
||||
bool TreatNameAsRegex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the run mode this command gets executed with.
|
||||
/// </summary>
|
||||
@@ -90,6 +95,15 @@ namespace Discord.Interactions.Builders
|
||||
/// </returns>
|
||||
ICommandBuilder SetRunMode (RunMode runMode);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="TreatNameAsRegex"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">New value of the <see cref="TreatNameAsRegex"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
ICommandBuilder WithNameAsRegex(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds parameter builders to <see cref="Parameters"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -274,6 +274,7 @@ namespace Discord.Interactions.Builders
|
||||
builder.Name = interaction.CustomId;
|
||||
builder.RunMode = interaction.RunMode;
|
||||
builder.IgnoreGroupNames = interaction.IgnoreGroupNames;
|
||||
builder.TreatNameAsRegex = interaction.TreatAsRegex;
|
||||
}
|
||||
break;
|
||||
case PreconditionAttribute precondition:
|
||||
@@ -287,7 +288,7 @@ namespace Discord.Interactions.Builders
|
||||
|
||||
var parameters = methodInfo.GetParameters();
|
||||
|
||||
var wildCardCount = Regex.Matches(Regex.Escape(builder.Name), Regex.Escape(commandService._wildCardExp)).Count;
|
||||
var wildCardCount = RegexUtils.GetWildCardCount(builder.Name, commandService._wildCardExp);
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
builder.AddParameter(x => BuildComponentParameter(x, parameter, parameter.Position >= wildCardCount));
|
||||
@@ -355,6 +356,7 @@ namespace Discord.Interactions.Builders
|
||||
builder.Name = modal.CustomId;
|
||||
builder.RunMode = modal.RunMode;
|
||||
builder.IgnoreGroupNames = modal.IgnoreGroupNames;
|
||||
builder.TreatNameAsRegex = modal.TreatAsRegex;
|
||||
}
|
||||
break;
|
||||
case PreconditionAttribute precondition:
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace Discord.Interactions
|
||||
/// <inheritdoc cref="ICommandInfo.Parameters"/>
|
||||
public abstract IReadOnlyList<TParameter> Parameters { get; }
|
||||
|
||||
public bool TreatNameAsRegex { get; }
|
||||
|
||||
internal CommandInfo(Builders.ICommandBuilder builder, ModuleInfo module, InteractionService commandService)
|
||||
{
|
||||
CommandService = commandService;
|
||||
@@ -78,6 +80,7 @@ namespace Discord.Interactions
|
||||
RunMode = builder.RunMode != RunMode.Default ? builder.RunMode : commandService._runMode;
|
||||
Attributes = builder.Attributes.ToImmutableArray();
|
||||
Preconditions = builder.Preconditions.ToImmutableArray();
|
||||
TreatNameAsRegex = builder.TreatNameAsRegex && SupportsWildCards;
|
||||
|
||||
_action = builder.Callback;
|
||||
_groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal);
|
||||
|
||||
@@ -65,6 +65,8 @@ namespace Discord.Interactions
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IParameterInfo> Parameters { get; }
|
||||
|
||||
bool TreatNameAsRegex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command with the provided context.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,14 +2,13 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Discord.Interactions
|
||||
{
|
||||
internal class CommandMapNode<T> where T : class, ICommandInfo
|
||||
{
|
||||
private const string RegexWildCardExp = "(\\S+)?";
|
||||
|
||||
{
|
||||
private readonly string _wildCardStr = "*";
|
||||
private readonly ConcurrentDictionary<string, CommandMapNode<T>> _nodes;
|
||||
private readonly ConcurrentDictionary<string, T> _commands;
|
||||
@@ -35,10 +34,8 @@ namespace Discord.Interactions
|
||||
{
|
||||
if (keywords.Count == index + 1)
|
||||
{
|
||||
if (commandInfo.SupportsWildCards && commandInfo.Name.Contains(_wildCardStr))
|
||||
if (commandInfo.SupportsWildCards && RegexUtils.TryBuildRegexPattern(commandInfo, _wildCardStr, out var patternStr))
|
||||
{
|
||||
var escapedStr = RegexUtils.EscapeExcluding(commandInfo.Name, _wildCardStr.ToArray());
|
||||
var patternStr = "\\A" + escapedStr.Replace(_wildCardStr, RegexWildCardExp) + "\\Z";
|
||||
var regex = new Regex(patternStr, RegexOptions.Singleline | RegexOptions.Compiled);
|
||||
|
||||
if (!_wildCardCommands.TryAdd(regex, commandInfo))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Discord.Interactions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
@@ -81,5 +82,37 @@ namespace System.Text.RegularExpressions
|
||||
{
|
||||
return (ch <= '|' && _category[ch] >= E);
|
||||
}
|
||||
|
||||
internal static int GetWildCardCount(string input, string wildCardExpression)
|
||||
{
|
||||
var escapedWildCard = Regex.Escape(wildCardExpression);
|
||||
var match = Regex.Matches(input, $@"(?<!\\){escapedWildCard}|(?<!\\){{[0-9]+(?:,[0-9]*)?(?<!\\)}}");
|
||||
return match.Count;
|
||||
}
|
||||
|
||||
internal static bool TryBuildRegexPattern<T>(T commandInfo, string wildCardStr, out string pattern) where T: class, ICommandInfo
|
||||
{
|
||||
if (commandInfo.TreatNameAsRegex)
|
||||
{
|
||||
pattern = commandInfo.Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetWildCardCount(commandInfo.Name, wildCardStr) == 0)
|
||||
{
|
||||
pattern = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var escapedWildCard = Regex.Escape(wildCardStr);
|
||||
var unquantified = Regex.Replace(commandInfo.Name, $@"(?<!\\){escapedWildCard}(?<delimiter>[^{escapedWildCard}]?)",
|
||||
@"([^\n\t${delimiter}]+)${delimiter}");
|
||||
|
||||
var quantified = Regex.Replace(unquantified, $@"(?<!\\){{(?<start>[0-9]+)(?<end>,[0-9]*)?(?<!\\)}}(?<delimiter>[^{escapedWildCard}]?)",
|
||||
@"([^\n\t${delimiter}]{${start}${end}})${delimiter}");
|
||||
|
||||
pattern = "\\A" + quantified + "\\Z";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user