Skip to content

Commit 70303d7

Browse files
committed
Adding Recursion depth control to ModelStateDictionary
1 parent 4d0b372 commit 70303d7

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelStateDictionary.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ public class ModelStateDictionary : IReadOnlyDictionary<string, ModelStateEntry>
2424
/// </summary>
2525
public static readonly int DefaultMaxAllowedErrors = 200;
2626

27+
private const int DefaultMaxRecursionDepth = 32;
2728
private const char DelimiterDot = '.';
2829
private const char DelimiterOpen = '[';
2930

3031
private readonly ModelStateNode _root;
3132
private int _maxAllowedErrors;
33+
private int _maxRecursionDepth;
3234

3335
/// <summary>
3436
/// Initializes a new instance of the <see cref="ModelStateDictionary"/> class.
@@ -44,6 +46,7 @@ public ModelStateDictionary()
4446
public ModelStateDictionary(int maxAllowedErrors)
4547
{
4648
MaxAllowedErrors = maxAllowedErrors;
49+
MaxRecursionDepth = DefaultMaxRecursionDepth;
4750
var emptySegment = new StringSegment(buffer: string.Empty);
4851
_root = new ModelStateNode(subKey: emptySegment)
4952
{
@@ -153,7 +156,7 @@ public bool IsValid
153156
}
154157

155158
/// <inheritdoc />
156-
public ModelValidationState ValidationState => GetValidity(_root) ?? ModelValidationState.Valid;
159+
public ModelValidationState ValidationState => GetValidity(_root, currentDepth: 0) ?? ModelValidationState.Valid;
157160

158161
/// <inheritdoc />
159162
public ModelStateEntry this[string key]
@@ -173,6 +176,22 @@ public ModelStateEntry this[string key]
173176
// Flag that indicates if TooManyModelErrorException has already been added to this dictionary.
174177
private bool HasRecordedMaxModelError { get; set; }
175178

179+
internal int MaxRecursionDepth
180+
{
181+
get
182+
{
183+
return _maxRecursionDepth;
184+
}
185+
set
186+
{
187+
if (value < 0)
188+
{
189+
throw new ArgumentOutOfRangeException(nameof(value));
190+
}
191+
_maxRecursionDepth = value;
192+
}
193+
}
194+
176195
/// <summary>
177196
/// Adds the specified <paramref name="exception"/> to the <see cref="ModelStateEntry.Errors"/> instance
178197
/// that is associated with the specified <paramref name="key"/>. If the maximum number of allowed
@@ -409,7 +428,7 @@ public ModelValidationState GetFieldValidationState(string key)
409428
}
410429

411430
var item = GetNode(key);
412-
return GetValidity(item) ?? ModelValidationState.Unvalidated;
431+
return GetValidity(item, currentDepth: 0) ?? ModelValidationState.Unvalidated;
413432
}
414433

415434
/// <summary>
@@ -661,9 +680,9 @@ private static StringSegment FindNext(string key, ref MatchResult currentMatch)
661680
return new StringSegment(key, keyStart, index - keyStart);
662681
}
663682

664-
private static ModelValidationState? GetValidity(ModelStateNode node)
683+
private static ModelValidationState? GetValidity(ModelStateNode node, int currentDepth)
665684
{
666-
if (node == null)
685+
if (node == null || currentDepth >= MaxRecursionDepth)
667686
{
668687
return null;
669688
}
@@ -686,9 +705,11 @@ private static StringSegment FindNext(string key, ref MatchResult currentMatch)
686705

687706
if (node.ChildNodes != null)
688707
{
708+
currentDepth++;
709+
689710
for (var i = 0; i < node.ChildNodes.Count; i++)
690711
{
691-
var entryState = GetValidity(node.ChildNodes[i]);
712+
var entryState = GetValidity(node.ChildNodes[i], currentDepth);
692713

693714
if (entryState == ModelValidationState.Unvalidated)
694715
{

src/Mvc/Mvc.Core/src/Infrastructure/ControllerActionInvokerProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class ControllerActionInvokerProvider : IActionInvokerProvider
1818
private readonly ControllerActionInvokerCache _controllerActionInvokerCache;
1919
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
2020
private readonly int _maxModelValidationErrors;
21+
private readonly int _maxModelBindingRecursionDepth;
2122
private readonly ILogger _logger;
2223
private readonly DiagnosticListener _diagnosticListener;
2324
private readonly IActionResultTypeMapper _mapper;
@@ -44,6 +45,7 @@ public ControllerActionInvokerProvider(
4445
_controllerActionInvokerCache = controllerActionInvokerCache;
4546
_valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray();
4647
_maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors;
48+
_maxModelBindingRecursionDepth = optionsAccessor.Value.MaxModelBindingRecursionDepth;
4749
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
4850
_diagnosticListener = diagnosticListener;
4951
_mapper = mapper;
@@ -68,6 +70,7 @@ public void OnProvidersExecuting(ActionInvokerProviderContext context)
6870
ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories)
6971
};
7072
controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
73+
controllerContext.ModelState.MaxRecursionDepth = _maxModelBindingRecursionDepth;
7174

7275
var (cacheEntry, filters) = _controllerActionInvokerCache.GetCachedResult(controllerContext);
7376

0 commit comments

Comments
 (0)