diff --git a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
index fea49bcc9a5..0e9df1a4785 100644
--- a/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
+++ b/src/HotChocolate/Core/src/Execution/DependencyInjection/RequestExecutorServiceCollectionExtensions.cs
@@ -94,9 +94,12 @@ public static IServiceCollection AddGraphQLCore(this IServiceCollection services
return new ParserOptions(
noLocations: !options.IncludeLocations,
+ allowFragmentVariables: false,
maxAllowedNodes: options.MaxAllowedNodes,
maxAllowedTokens: options.MaxAllowedTokens,
- maxAllowedFields: options.MaxAllowedFields);
+ maxAllowedFields: options.MaxAllowedFields,
+ maxAllowedDirectives: options.MaxAllowedDirectives,
+ maxAllowedRecursionDepth: options.MaxAllowedRecursionDepth);
});
return services;
diff --git a/src/HotChocolate/Core/src/Execution/Options/RequestParserOptions.cs b/src/HotChocolate/Core/src/Execution/Options/RequestParserOptions.cs
index bdaf9e0aaa0..a243b6dcdb1 100644
--- a/src/HotChocolate/Core/src/Execution/Options/RequestParserOptions.cs
+++ b/src/HotChocolate/Core/src/Execution/Options/RequestParserOptions.cs
@@ -50,4 +50,17 @@ public sealed class RequestParserOptions
/// as fields is an easier way to estimate query size for GraphQL requests.
///
public int MaxAllowedFields { get; set; } = 2048;
+
+ ///
+ /// The maximum number of directives allowed per location (e.g. per field,
+ /// per operation, per fragment definition). Repeatable directives can be used
+ /// to exhaust CPU and memory resources if not limited.
+ ///
+ public int MaxAllowedDirectives { get; set; } = 4;
+
+ ///
+ /// The maximum allowed recursion depth when parsing a document.
+ /// This prevents stack overflow from deeply nested queries.
+ ///
+ public int MaxAllowedRecursionDepth { get; set; } = 200;
}
diff --git a/src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs b/src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs
index 1c5ebac76d5..f21045a3e4b 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/ParserOptions.cs
@@ -45,6 +45,51 @@ public ParserOptions(
MaxAllowedTokens = maxAllowedTokens;
MaxAllowedNodes = maxAllowedNodes;
MaxAllowedFields = maxAllowedFields;
+ MaxAllowedDirectives = 4;
+ MaxAllowedRecursionDepth = 200;
+ }
+
+ ///
+ /// Initializes a new instance of with security limits.
+ ///
+ ///
+ /// Defines that the parse shall not preserve syntax node locations.
+ ///
+ ///
+ /// Defines that the parser shall parse fragment variables.
+ ///
+ ///
+ /// The maximum number of nodes allowed within a document.
+ ///
+ ///
+ /// The maximum number of tokens allowed within a document.
+ ///
+ ///
+ /// The maximum number of fields allowed within a query document.
+ ///
+ ///
+ /// The maximum number of directives allowed per location (e.g. per field,
+ /// per operation, per fragment definition).
+ ///
+ ///
+ /// The maximum allowed recursion depth of a parsed document.
+ ///
+ public ParserOptions(
+ bool noLocations,
+ bool allowFragmentVariables,
+ int maxAllowedNodes,
+ int maxAllowedTokens,
+ int maxAllowedFields,
+ int maxAllowedDirectives,
+ int maxAllowedRecursionDepth)
+ {
+ NoLocations = noLocations;
+ Experimental = new(allowFragmentVariables);
+ MaxAllowedTokens = maxAllowedTokens;
+ MaxAllowedNodes = maxAllowedNodes;
+ MaxAllowedFields = maxAllowedFields;
+ MaxAllowedDirectives = maxAllowedDirectives;
+ MaxAllowedRecursionDepth = maxAllowedRecursionDepth;
}
///
@@ -86,6 +131,18 @@ public ParserOptions(
///
public int MaxAllowedFields { get; }
+ ///
+ /// The maximum number of directives allowed per location (e.g. per field,
+ /// per operation, per fragment definition). Repeatable directives can be used
+ /// to exhaust CPU and memory resources if not limited.
+ ///
+ public int MaxAllowedDirectives { get; }
+
+ ///
+ /// Gets the maximum allowed recursion depth of a parsed document.
+ ///
+ public int MaxAllowedRecursionDepth { get; }
+
///
/// Gets the experimental parser options
/// which are by default switched of.
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.Designer.cs b/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.Designer.cs
index 52181d1bd86..a59b05d96e3 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.Designer.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.Designer.cs
@@ -224,5 +224,17 @@ internal static string Utf8GraphQLParser_Start_MaxAllowedFieldsReached {
return ResourceManager.GetString("Utf8GraphQLParser_Start_MaxAllowedFieldsReached", resourceCulture);
}
}
+
+ internal static string Utf8GraphQLParser_ParseDirective_MaxAllowedDirectivesReached {
+ get {
+ return ResourceManager.GetString("Utf8GraphQLParser_ParseDirective_MaxAllowedDirectivesReached", resourceCulture);
+ }
+ }
+
+ internal static string Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached {
+ get {
+ return ResourceManager.GetString("Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached", resourceCulture);
+ }
+ }
}
}
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.resx b/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.resx
index feaa997ae7e..464706cef98 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.resx
+++ b/src/HotChocolate/Language/src/Language.Utf8/Properties/LangUtf8Resources.resx
@@ -207,4 +207,10 @@
The GraphQL request document contains more than {0} fields. Parsing aborted.
+
+ A location in the GraphQL document contains more than {0} directives. Parsing aborted.
+
+
+ Document exceeds the maximum allowed recursion depth of {0}. Parsing aborted.
+
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Directives.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Directives.cs
index 196b973ae3e..a3391286d6e 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Directives.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Directives.cs
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
+using static HotChocolate.Language.Properties.LangUtf8Resources;
namespace HotChocolate.Language;
@@ -64,7 +65,7 @@ private NameNode ParseDirectiveLocation()
throw Unexpected(kind);
}
- private List ParseDirectives(bool isConstant)
+ private List ParseDirectives(bool isConstant, bool isQueryLocation = false)
{
if (_reader.Kind == TokenKind.At)
{
@@ -73,6 +74,15 @@ private List ParseDirectives(bool isConstant)
while (_reader.Kind == TokenKind.At)
{
list.Add(ParseDirective(isConstant));
+
+ if (isQueryLocation && list.Count > _maxAllowedDirectives)
+ {
+ throw new SyntaxException(
+ _reader,
+ string.Format(
+ Utf8GraphQLParser_ParseDirective_MaxAllowedDirectivesReached,
+ _maxAllowedDirectives));
+ }
}
return list;
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Fragments.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Fragments.cs
index bde32c0890a..6dfa9900c13 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Fragments.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Fragments.cs
@@ -53,7 +53,7 @@ private FragmentDefinitionNode ParseFragmentDefinition()
ParseVariableDefinitions();
ExpectOnKeyword();
var typeCondition = ParseNamedType();
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var selectionSet = ParseSelectionSet();
var location = CreateLocation(in start);
@@ -72,7 +72,7 @@ private FragmentDefinitionNode ParseFragmentDefinition()
var name = ParseFragmentName();
ExpectOnKeyword();
var typeCondition = ParseNamedType();
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var selectionSet = ParseSelectionSet();
var location = CreateLocation(in start);
@@ -99,7 +99,7 @@ private FragmentDefinitionNode ParseFragmentDefinition()
private FragmentSpreadNode ParseFragmentSpread(in TokenInfo start)
{
var name = ParseFragmentName();
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var location = CreateLocation(in start);
return new FragmentSpreadNode
@@ -125,7 +125,7 @@ private InlineFragmentNode ParseInlineFragment(
in TokenInfo start,
NamedTypeNode? typeCondition)
{
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var selectionSet = ParseSelectionSet();
var location = CreateLocation(in start);
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Operations.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Operations.cs
index a82e452346c..882a106ebb9 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Operations.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Operations.cs
@@ -21,7 +21,7 @@ private OperationDefinitionNode ParseOperationDefinition()
var operation = ParseOperationType();
var name = _reader.Kind == TokenKind.Name ? ParseName() : null;
var variableDefinitions = ParseVariableDefinitions();
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var selectionSet = ParseSelectionSet();
var location = CreateLocation(in start);
@@ -127,7 +127,7 @@ private VariableDefinitionNode ParseVariableDefinition()
? ParseValueLiteral(true)
: null;
var directives =
- ParseDirectives(isConstant: true);
+ ParseDirectives(isConstant: true, isQueryLocation: true);
var location = CreateLocation(in start);
@@ -163,6 +163,7 @@ private VariableNode ParseVariable()
///
private SelectionSetNode ParseSelectionSet()
{
+ IncreaseDepth();
var start = Start();
if (_reader.Kind != TokenKind.LeftBrace)
@@ -191,6 +192,7 @@ private SelectionSetNode ParseSelectionSet()
var location = CreateLocation(in start);
+ DecreaseDepth();
return new SelectionSetNode(
location,
selections);
@@ -240,7 +242,7 @@ private FieldNode ParseField()
}
var arguments = ParseArguments(false);
- var directives = ParseDirectives(false);
+ var directives = ParseDirectives(false, isQueryLocation: true);
var selectionSet = _reader.Kind == TokenKind.LeftBrace
? ParseSelectionSet()
: null;
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Types.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Types.cs
index 854e8da30b4..2d0b3da063a 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Types.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Types.cs
@@ -12,6 +12,7 @@ public ref partial struct Utf8GraphQLParser
///
private ITypeNode ParseTypeReference()
{
+ IncreaseDepth();
ITypeNode type;
Location? location;
@@ -40,6 +41,7 @@ private ITypeNode ParseTypeReference()
MoveNext();
location = CreateLocation(in start);
+ DecreaseDepth();
return new NonNullTypeNode
(
location,
@@ -50,6 +52,7 @@ private ITypeNode ParseTypeReference()
Unexpected(TokenKind.Bang);
}
+ DecreaseDepth();
return type;
}
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs
index a7be817cc30..134ffe74639 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Utilities.cs
@@ -24,6 +24,25 @@ private NameNode ParseName()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool MoveNext() => _reader.MoveNext();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void IncreaseDepth()
+ {
+ if (++_recursionDepth > _maxAllowedRecursionDepth)
+ {
+ throw new SyntaxException(
+ _reader,
+ string.Format(
+ Utf8GraphQLParser_Start_MaxAllowedRecursionDepthReached,
+ _maxAllowedRecursionDepth));
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void DecreaseDepth()
+ {
+ --_recursionDepth;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TokenInfo Start()
{
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs
index 7ba6b89645a..c1400e282cf 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.Values.cs
@@ -29,32 +29,37 @@ public ref partial struct Utf8GraphQLParser
///
private IValueNode ParseValueLiteral(bool isConstant)
{
+ IncreaseDepth();
+
+ IValueNode node;
+
if (_reader.Kind == TokenKind.LeftBracket)
{
- return ParseList(isConstant);
+ node = ParseList(isConstant);
}
-
- if (_reader.Kind == TokenKind.LeftBrace)
+ else if (_reader.Kind == TokenKind.LeftBrace)
{
- return ParseObject(isConstant);
+ node = ParseObject(isConstant);
}
-
- if (TokenHelper.IsScalarValue(ref _reader))
+ else if (TokenHelper.IsScalarValue(ref _reader))
{
- return ParseScalarValue();
+ node = ParseScalarValue();
}
-
- if (_reader.Kind == TokenKind.Name)
+ else if (_reader.Kind == TokenKind.Name)
{
- return ParseEnumValue();
+ node = ParseEnumValue();
}
-
- if (_reader.Kind == TokenKind.Dollar && !isConstant)
+ else if (_reader.Kind == TokenKind.Dollar && !isConstant)
+ {
+ node = ParseVariable();
+ }
+ else
{
- return ParseVariable();
+ throw Unexpected(_reader.Kind);
}
- throw Unexpected(_reader.Kind);
+ DecreaseDepth();
+ return node;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs
index 997d57029f0..b1f1b564200 100644
--- a/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs
+++ b/src/HotChocolate/Language/src/Language.Utf8/Utf8GraphQLParser.cs
@@ -10,10 +10,13 @@ public ref partial struct Utf8GraphQLParser
private readonly bool _allowFragmentVars;
private readonly int _maxAllowedNodes;
private readonly int _maxAllowedFields;
+ private readonly int _maxAllowedDirectives;
+ private readonly int _maxAllowedRecursionDepth;
private Utf8GraphQLReader _reader;
private StringValueNode? _description;
private int _parsedNodes;
private int _parsedFields;
+ private int _recursionDepth;
public Utf8GraphQLParser(
ReadOnlySpan graphQLData,
@@ -29,6 +32,8 @@ public Utf8GraphQLParser(
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_maxAllowedNodes = options.MaxAllowedNodes;
_maxAllowedFields = options.MaxAllowedFields;
+ _maxAllowedDirectives = options.MaxAllowedDirectives;
+ _maxAllowedRecursionDepth = options.MaxAllowedRecursionDepth;
_reader = new Utf8GraphQLReader(graphQLData, options.MaxAllowedTokens);
_description = null;
}
@@ -47,6 +52,8 @@ internal Utf8GraphQLParser(
_allowFragmentVars = options.Experimental.AllowFragmentVariables;
_maxAllowedNodes = options.MaxAllowedNodes;
_maxAllowedFields = options.MaxAllowedFields;
+ _maxAllowedDirectives = options.MaxAllowedDirectives;
+ _maxAllowedRecursionDepth = options.MaxAllowedRecursionDepth;
_reader = reader;
_description = null;
}
@@ -64,6 +71,7 @@ internal Utf8GraphQLParser(
public DocumentNode Parse()
{
_parsedNodes = 0;
+ _recursionDepth = 0;
var definitions = new List();
var start = Start();
diff --git a/src/HotChocolate/Language/test/Language.Tests/Parser/QueryParserTests.cs b/src/HotChocolate/Language/test/Language.Tests/Parser/QueryParserTests.cs
index 1c0f32737ba..48014eb06d3 100644
--- a/src/HotChocolate/Language/test/Language.Tests/Parser/QueryParserTests.cs
+++ b/src/HotChocolate/Language/test/Language.Tests/Parser/QueryParserTests.cs
@@ -6,6 +6,209 @@ namespace HotChocolate.Language;
public class QueryParserTests
{
+ [Fact]
+ public void Default_MaxAllowedRecursionDepth_Is_200()
+ {
+ Assert.Equal(200, ParserOptions.Default.MaxAllowedRecursionDepth);
+ }
+
+ [Fact]
+ public void Reject_Queries_Exceeding_Max_Recursion_Depth_Selection_Sets()
+ {
+ const int depth = 201;
+ var query = string.Concat(Enumerable.Repeat("{ a", depth))
+ + string.Concat(Enumerable.Repeat(" }", depth));
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse(query))
+ .Message
+ .MatchInlineSnapshot(
+ "Document exceeds the maximum allowed recursion depth of 200. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Reject_Queries_Exceeding_Max_Recursion_Depth_Object_Values()
+ {
+ const int depth = 201;
+ var query = "{ a(x: "
+ + string.Concat(Enumerable.Repeat("{a: ", depth))
+ + "1"
+ + string.Concat(Enumerable.Repeat("}", depth))
+ + ") }";
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse(query))
+ .Message
+ .MatchInlineSnapshot(
+ "Document exceeds the maximum allowed recursion depth of 200. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Reject_Queries_Exceeding_Max_Recursion_Depth_List_Values()
+ {
+ const int depth = 201;
+ var query = "{ a(x: "
+ + string.Concat(Enumerable.Repeat("[", depth))
+ + "1"
+ + string.Concat(Enumerable.Repeat("]", depth))
+ + ") }";
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse(query))
+ .Message
+ .MatchInlineSnapshot(
+ "Document exceeds the maximum allowed recursion depth of 200. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Reject_Queries_Exceeding_Max_Recursion_Depth_List_Types()
+ {
+ const int depth = 201;
+ var query = $"query($v: {string.Concat(Enumerable.Repeat("[", depth))}Int{string.Concat(Enumerable.Repeat("]", depth))}) {{ a }}";
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse(query))
+ .Message
+ .MatchInlineSnapshot(
+ "Document exceeds the maximum allowed recursion depth of 200. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Allow_Queries_Within_Max_Recursion_Depth()
+ {
+ const int depth = 50;
+ var query = string.Concat(Enumerable.Repeat("{ a", depth))
+ + string.Concat(Enumerable.Repeat(" }", depth));
+
+ var document = Utf8GraphQLParser.Parse(query);
+
+ Assert.NotNull(document);
+ Assert.Single(document.Definitions);
+ }
+
+ [Fact]
+ public void Reject_Queries_Exceeding_Custom_Recursion_Depth()
+ {
+ var options = new ParserOptions(
+ noLocations: false,
+ allowFragmentVariables: false,
+ maxAllowedNodes: int.MaxValue,
+ maxAllowedTokens: int.MaxValue,
+ maxAllowedFields: 2048,
+ maxAllowedDirectives: 4,
+ maxAllowedRecursionDepth: 10);
+ const int depth = 11;
+ var query = string.Concat(Enumerable.Repeat("{ a", depth))
+ + string.Concat(Enumerable.Repeat(" }", depth));
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse(query, options))
+ .Message
+ .MatchInlineSnapshot(
+ "Document exceeds the maximum allowed recursion depth of 10. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Allow_Queries_Within_Custom_Recursion_Depth()
+ {
+ var options = new ParserOptions(
+ noLocations: false,
+ allowFragmentVariables: false,
+ maxAllowedNodes: int.MaxValue,
+ maxAllowedTokens: int.MaxValue,
+ maxAllowedFields: 2048,
+ maxAllowedDirectives: 4,
+ maxAllowedRecursionDepth: 10);
+ const int depth = 10;
+ var query = string.Concat(Enumerable.Repeat("{ a", depth))
+ + string.Concat(Enumerable.Repeat(" }", depth));
+
+ var document = Utf8GraphQLParser.Parse(query, options);
+
+ Assert.NotNull(document);
+ Assert.Single(document.Definitions);
+ }
+
+ [Theory]
+ [InlineData(20_000)]
+ [InlineData(50_000)]
+ public void Reject_Attack_Payload_Nested_Selection_Sets(int depth)
+ {
+ var query = string.Concat(Enumerable.Repeat("{ a", depth))
+ + string.Concat(Enumerable.Repeat(" }", depth));
+
+ Assert.Throws(() => Utf8GraphQLParser.Parse(query));
+ }
+
+ [Theory]
+ [InlineData(20_000)]
+ [InlineData(50_000)]
+ public void Reject_Attack_Payload_Nested_List_Values(int depth)
+ {
+ var query = "{ a(x: "
+ + string.Concat(Enumerable.Repeat("[", depth))
+ + "1"
+ + string.Concat(Enumerable.Repeat("]", depth))
+ + ") }";
+
+ Assert.Throws(() => Utf8GraphQLParser.Parse(query));
+ }
+
+ [Fact]
+ public void Default_MaxAllowedDirectives_Is_4()
+ {
+ Assert.Equal(4, ParserOptions.Default.MaxAllowedDirectives);
+ }
+
+ [Fact]
+ public void Reject_Fields_Exceeding_Max_Allowed_Directives_Per_Location()
+ {
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse("{ a @d @d @d @d @d }"))
+ .Message
+ .MatchInlineSnapshot(
+ "A location in the GraphQL document contains more than 4 directives. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Allow_Fields_Within_Max_Allowed_Directives_Per_Location()
+ {
+ Utf8GraphQLParser.Parse("{ a @d @d @d @d }");
+ }
+
+ [Fact]
+ public void Reject_Fields_Exceeding_Custom_Directive_Limit()
+ {
+ var options = new ParserOptions(
+ noLocations: false,
+ allowFragmentVariables: false,
+ maxAllowedNodes: int.MaxValue,
+ maxAllowedTokens: int.MaxValue,
+ maxAllowedFields: 2048,
+ maxAllowedDirectives: 2,
+ maxAllowedRecursionDepth: 200);
+
+ Assert
+ .Throws(() => Utf8GraphQLParser.Parse("{ a @d @d @d }", options))
+ .Message
+ .MatchInlineSnapshot(
+ "A location in the GraphQL document contains more than 2 directives. Parsing aborted.");
+ }
+
+ [Fact]
+ public void Allow_Fields_Within_Custom_Directive_Limit()
+ {
+ var options = new ParserOptions(
+ noLocations: false,
+ allowFragmentVariables: false,
+ maxAllowedNodes: int.MaxValue,
+ maxAllowedTokens: int.MaxValue,
+ maxAllowedFields: 2048,
+ maxAllowedDirectives: 2,
+ maxAllowedRecursionDepth: 200);
+ Utf8GraphQLParser.Parse("{ a @d @d }", options);
+ }
+
[Fact]
public void Reject_Queries_With_More_Than_2048_Fields()
{;