Add JsonSelectSettings and regex timeout

This commit is contained in:
James Newton-King
2021-01-17 18:23:19 +13:00
parent 95a6eb3ac5
commit 42139ea6cd
20 changed files with 108 additions and 193 deletions
-2
View File
@@ -22,7 +22,5 @@
<SystemValueTuplePackageVersion>4.4.0</SystemValueTuplePackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.3.1</XunitRunnerVisualStudioPackageVersion>
<BogusPackageVersion>32.0.2</BogusPackageVersion>
<AsyncExPackageVersion>5.1.0</AsyncExPackageVersion>
</PropertyGroup>
</Project>
@@ -32,11 +32,7 @@ using System.Numerics;
using Newtonsoft.Json.Linq.JsonPath;
using Newtonsoft.Json.Tests.Bson;
#if HAVE_REGEX_TIMEOUTS
using Bogus;
using Nito.AsyncEx;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
#endif
#if DNXCORE50
using Xunit;
@@ -81,69 +77,21 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath
[Test]
public void BacktrackingRegex_SingleMatch_TimeoutRespected()
{
var RegexBacktrackingPattern = "(?<a>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
const string RegexBacktrackingPattern = "(?<a>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
var faker = new Faker();
var regexBacktrackingData = new JArray();
regexBacktrackingData.Add(new JObject(new JProperty("b", @"15/04/2020 8:18:03 PM|1|System.String[]|3|Libero eligendi magnam ut inventore.. Quaerat et sit voluptatibus repellendus blanditiis aliquam ut.. Quidem qui ut sint in ex et tempore.|||.\iste.cpp||46018|-1")));
for (var i = 0; i < 1000; i++)
ExceptionAssert.Throws<RegexMatchTimeoutException>(() =>
{
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
}
Xunit.Assert.Throws<RegexMatchTimeoutException>(() =>
{
var tokens = regexBacktrackingData.SelectTokens(
regexBacktrackingData.SelectTokens(
$"[?(@.b =~ /{RegexBacktrackingPattern}/)]",
errorWhenNoMatch: false,
singleRegexMatchTimeout: TimeSpan.FromSeconds(.01)).ToArray();
new JsonSelectSettings
{
RegexMatchTimeout = TimeSpan.FromSeconds(0.01)
}).ToArray();
});
}
[Test]
public void BacktrackingRegex_GlobalMatch_TimeoutRespected()
{
var faker = new Faker();
var regexBacktrackingData = new JArray();
for (var i = 0; i < 1000; i++)
{
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
}
var RegexBacktrackingPattern = "(?<i>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
var jpathExpression = $"[?(@.b =~ /{RegexBacktrackingPattern}/c)]";
var exceptionThrow = Xunit.Assert.ThrowsAny<Exception>(() =>
{
var tokens = regexBacktrackingData.SelectTokens(
jpathExpression,
errorWhenNoMatch: false,
singleRegexMatchTimeout: TimeSpan.FromSeconds(2),
globalRegexMatchTimeout: TimeSpan.FromSeconds(.5)).ToArray();
});
Assert.IsTrue(exceptionThrow is OperationCanceledException or RegexMatchTimeoutException);
}
[Test]
public async Task BacktrackingRegexCanBeVerySlowWithoutTimeouts()
{
var RegexBacktrackingPattern = "(?<j>(.*?))[|].*(?<b>(.*?))[|].*(?<c>(.*?))[|].*(?<d>[1-3])[|].*(?<e>(.*?))[|].*[|].*[|].*(?<f>(.*?))[|].*[|].*(?<g>(.*?))[|].*(?<h>(.*))";
var faker = new Faker();
var regexBacktrackingData = new JArray();
for (var i = 0; i < 10; i++)
{
var value = $"{faker.Date.Past()}|1|{faker.Lorem.Words()}|3|{faker.Lorem.Sentences(3, ". ")}|||.\\{faker.Lorem.Word()}.cpp||{faker.Random.UShort()}|-1";
regexBacktrackingData.Add(new JObject(new JProperty("b", value)));
}
var selectTokenTask = Task.Run(() => regexBacktrackingData.SelectTokens($"[?(@.b =~ /{RegexBacktrackingPattern}/)]").ToArray());
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(.3));
using var ctts = new CancellationTokenTaskSource<bool>(cts.Token);
var finishedTask = await Task.WhenAny(selectTokenTask, ctts.Task);
Assert.AreEqual(finishedTask, ctts.Task);
}
#endif
[Test]
@@ -62,7 +62,7 @@
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
<AssemblyTitle>Json.NET Tests</AssemblyTitle>
<ReferringTargetFrameworkForProjectReferences>.NETFramework,Version=v4.5</ReferringTargetFrameworkForProjectReferences>
<DefineConstants>NET45;HAVE_BENCHMARKS;$(AdditionalConstants)</DefineConstants>
<DefineConstants>NET45;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net40'">
@@ -76,7 +76,6 @@
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Data.DataSetExtensions" />
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net40'">
<AssemblyTitle>Json.NET Tests .NET 4.0</AssemblyTitle>
@@ -122,10 +121,7 @@
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net5.0'">
<AssemblyTitle>Json.NET Tests .NET Standard 2.0</AssemblyTitle>
<ReferringTargetFrameworkForProjectReferences>.NETStandard,Version=v2.0</ReferringTargetFrameworkForProjectReferences>
@@ -146,8 +142,6 @@
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<AssemblyTitle>Json.NET Tests .NET Standard 1.3</AssemblyTitle>
@@ -168,10 +162,7 @@
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Bogus" Version="$(BogusPackageVersion)" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="$(AsyncExPackageVersion)" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp2.1'">
<AssemblyTitle>Json.NET Tests .NET Standard 1.0</AssemblyTitle>
<ReferringTargetFrameworkForProjectReferences>.NETStandard,Version=v1.0</ReferringTargetFrameworkForProjectReferences>
+19 -56
View File
@@ -44,7 +44,6 @@ using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json.Utilities.LinqBridge;
#else
using System.Linq;
using System.Threading;
#endif
namespace Newtonsoft.Json.Linq
@@ -2308,7 +2307,7 @@ namespace Newtonsoft.Json.Linq
/// <returns>A <see cref="JToken"/>, or <c>null</c>.</returns>
public JToken? SelectToken(string path)
{
return SelectToken(path, false);
return SelectToken(path, settings: null);
}
/// <summary>
@@ -2321,38 +2320,27 @@ namespace Newtonsoft.Json.Linq
/// <returns>A <see cref="JToken"/>.</returns>
public JToken? SelectToken(string path, bool errorWhenNoMatch)
{
JPath p = new JPath(path);
JsonSelectSettings? settings = errorWhenNoMatch
? new JsonSelectSettings { ErrorWhenNoMatch = true }
: null;
JToken? token = null;
foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch))
{
if (token != null)
{
throw new JsonException("Path returned multiple tokens.");
}
token = t;
}
return token;
return SelectToken(path, settings);
}
#if HAVE_REGEX_TIMEOUTS
/// <summary>
/// Selects a <see cref="JToken"/> using a JSONPath expression. Selects the token that matches the object path.
/// </summary>
/// <param name="path">
/// A <see cref="String"/> that contains a JSONPath expression.
/// </param>
/// <param name="errorWhenNoMatch">A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression.</param>
/// <param name="singleMatchTimeout">the time after which a single call to regex.ismatch must complete, default is forever</param>
/// <param name="settings">The <see cref="JsonSelectSettings"/> used to select tokens.</param>
/// <returns>A <see cref="JToken"/>.</returns>
public JToken? SelectToken(string path, bool errorWhenNoMatch, TimeSpan? singleMatchTimeout = default)
public JToken? SelectToken(string path, JsonSelectSettings? settings)
{
JPath p = new JPath(path, singleMatchTimeout);
JPath p = new JPath(path);
JToken? token = null;
foreach (JToken t in p.Evaluate(this, this, errorWhenNoMatch))
foreach (JToken t in p.Evaluate(this, this, settings))
{
if (token != null)
{
@@ -2364,7 +2352,6 @@ namespace Newtonsoft.Json.Linq
return token;
}
#endif
/// <summary>
/// Selects a collection of elements using a JSONPath expression.
@@ -2375,7 +2362,7 @@ namespace Newtonsoft.Json.Linq
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
public IEnumerable<JToken> SelectTokens(string path)
{
return SelectTokens(path, false);
return SelectTokens(path, settings: null);
}
/// <summary>
@@ -2388,50 +2375,26 @@ namespace Newtonsoft.Json.Linq
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
public IEnumerable<JToken> SelectTokens(string path, bool errorWhenNoMatch)
{
var p = new JPath(path);
return p.Evaluate(this, this, errorWhenNoMatch);
JsonSelectSettings? settings = errorWhenNoMatch
? new JsonSelectSettings { ErrorWhenNoMatch = true }
: null;
return SelectTokens(path, settings);
}
#if HAVE_REGEX_TIMEOUTS
/// <summary>
/// Selects a collection of elements using a JSONPath expression.
/// </summary>
/// <param name="path">
/// A <see cref="String"/> that contains a JSONPath expression.
/// </param>
/// <param name="errorWhenNoMatch">A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression.</param>
/// <param name="singleRegexMatchTimeout">
/// for every token that matches a jpath, the time a regex is permitted to run
/// against that token before timeout
/// </param>
/// <param name="globalRegexMatchTimeout">
/// the time this method should wait for the given jpath regex to match all tokens.
/// worst case expected execution time roughly globalRegexMatchTimeout + Min(singleRegexMatchTimeout, globalRegexMatchTimeout)
/// </param>
/// <exception cref="System.Text.RegularExpressions.RegexMatchTimeoutException">if single call timeout exceeded</exception>"
/// <param name="settings">The <see cref="JsonSelectSettings"/> used to select tokens.</param>
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="JToken"/> that contains the selected elements.</returns>
public IEnumerable<JToken> SelectTokens(string path,
bool errorWhenNoMatch,
TimeSpan? singleRegexMatchTimeout = default,
TimeSpan? globalRegexMatchTimeout = default)
public IEnumerable<JToken> SelectTokens(string path, JsonSelectSettings? settings)
{
var singleTimeout = singleRegexMatchTimeout ?? Timeout.InfiniteTimeSpan;
var globalTimeout = globalRegexMatchTimeout ?? Timeout.InfiniteTimeSpan;
if (globalTimeout != Timeout.InfiniteTimeSpan && globalTimeout < singleTimeout)
{
singleTimeout = globalTimeout;
}
using var cts = new CancellationTokenSource(globalTimeout);
var p = new JPath(path, singleTimeout);
var results = p.Evaluate(this, this, errorWhenNoMatch);
foreach (var result in results)
{
cts.Token.ThrowIfCancellationRequested();
yield return result;
}
var p = new JPath(path);
return p.Evaluate(this, this, settings);
}
#endif
#if HAVE_DYNAMIC
/// <summary>
@@ -8,13 +8,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
public int? Index { get; set; }
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
if (Index != null)
{
JToken? v = GetTokenIndex(t, errorWhenNoMatch, Index.GetValueOrDefault());
JToken? v = GetTokenIndex(t, settings, Index.GetValueOrDefault());
if (v != null)
{
@@ -32,7 +32,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Index * not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name));
}
@@ -11,13 +11,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
Indexes = indexes;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
foreach (int i in Indexes)
{
JToken? v = GetTokenIndex(t, errorWhenNoMatch, i);
JToken? v = GetTokenIndex(t, settings, i);
if (v != null)
{
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
public int? End { get; set; }
public int? Step { get; set; }
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
if (Step == 0)
{
@@ -56,7 +56,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Array slice of {0} to {1} returned no results.".FormatWith(CultureInfo.InvariantCulture,
Start != null ? Start.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "*",
@@ -66,7 +66,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Array slice is not valid on {0}.".FormatWith(CultureInfo.InvariantCulture, t.GetType().Name));
}
@@ -13,7 +13,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
Name = name;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
@@ -27,7 +27,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
yield return v;
}
else if (errorWhenNoMatch)
else if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, Name));
}
@@ -42,7 +42,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Property '{0}' not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, Name ?? "*", t.GetType().Name));
}
@@ -18,7 +18,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
Names = names;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
@@ -33,7 +33,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
yield return v;
}
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Property '{0}' does not exist on JObject.".FormatWith(CultureInfo.InvariantCulture, name));
}
@@ -41,7 +41,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Properties {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, string.Join(", ", Names.Select(n => "'" + n + "'")
#if !HAVE_STRING_JOIN_WITH_ENUMERABLE
+6 -11
View File
@@ -27,8 +27,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json.Linq.JsonPath
@@ -39,16 +37,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
private readonly string _expression;
public List<PathFilter> Filters { get; }
public readonly TimeSpan? RegexSingleMatchTimeout = default;
private int _currentIndex;
public JPath(string expression, TimeSpan? regexSingleMatchTimeout = default)
public JPath(string expression)
{
ValidationUtils.ArgumentNotNull(expression, nameof(expression));
_expression = expression;
Filters = new List<PathFilter>();
RegexSingleMatchTimeout = regexSingleMatchTimeout;
ParseMain();
}
@@ -513,8 +509,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
right = ParseSide();
}
var booleanExpression = new BooleanQueryExpression(op, left, right, RegexSingleMatchTimeout);
BooleanQueryExpression booleanExpression = new BooleanQueryExpression(op, left, right);
if (_expression[_currentIndex] == ')')
{
@@ -879,17 +874,17 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
}
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, bool errorWhenNoMatch)
internal IEnumerable<JToken> Evaluate(JToken root, JToken t, JsonSelectSettings? settings)
{
return Evaluate(Filters, root, t, errorWhenNoMatch);
return Evaluate(Filters, root, t, settings);
}
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, bool errorWhenNoMatch)
internal static IEnumerable<JToken> Evaluate(List<PathFilter> filters, JToken root, JToken t, JsonSelectSettings? settings)
{
IEnumerable<JToken> current = new[] { t };
foreach (PathFilter filter in filters)
{
current = filter.ExecuteFilter(root, current, errorWhenNoMatch);
current = filter.ExecuteFilter(root, current, settings);
}
return current;
@@ -6,15 +6,15 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
internal abstract class PathFilter
{
public abstract IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch);
public abstract IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings);
protected static JToken? GetTokenIndex(JToken t, bool errorWhenNoMatch, int index)
protected static JToken? GetTokenIndex(JToken t, JsonSelectSettings? settings, int index)
{
if (t is JArray a)
{
if (a.Count <= index)
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Index {0} outside the bounds of JArray.".FormatWith(CultureInfo.InvariantCulture, index));
}
@@ -28,7 +28,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
if (c.Count <= index)
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Index {0} outside the bounds of JConstructor.".FormatWith(CultureInfo.InvariantCulture, index));
}
@@ -40,7 +40,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (errorWhenNoMatch)
if (settings?.ErrorWhenNoMatch ?? false)
{
throw new JsonException("Index {0} not valid on {1}.".FormatWith(CultureInfo.InvariantCulture, index, t.GetType().Name));
}
@@ -4,7 +4,6 @@ using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Threading;
#if !HAVE_LINQ
using Newtonsoft.Json.Utilities.LinqBridge;
#else
@@ -40,7 +39,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
Operator = @operator;
}
public abstract bool IsMatch(JToken root, JToken t);
// For unit tests
public bool IsMatch(JToken root, JToken t)
{
return IsMatch(root, t, null);
}
public abstract bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings);
}
internal class CompositeExpression : QueryExpression
@@ -52,14 +57,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
Expressions = new List<QueryExpression>();
}
public override bool IsMatch(JToken root, JToken t)
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
{
switch (Operator)
{
case QueryOperator.And:
foreach (QueryExpression e in Expressions)
{
if (!e.IsMatch(root, t))
if (!e.IsMatch(root, t, settings))
{
return false;
}
@@ -68,7 +73,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
case QueryOperator.Or:
foreach (QueryExpression e in Expressions)
{
if (e.IsMatch(root, t))
if (e.IsMatch(root, t, settings))
{
return true;
}
@@ -84,17 +89,11 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
public readonly object Left;
public readonly object? Right;
public readonly TimeSpan? SingleMatchTimeout;
public BooleanQueryExpression(QueryOperator @operator,
object left,
object? right,
TimeSpan? regexSingleMatchTimeout = default)
: base(@operator)
public BooleanQueryExpression(QueryOperator @operator, object left, object? right) : base(@operator)
{
Left = left;
Right = right;
SingleMatchTimeout = regexSingleMatchTimeout;
}
private IEnumerable<JToken> GetResult(JToken root, JToken t, object? o)
@@ -106,13 +105,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
if (o is List<PathFilter> pathFilters)
{
return JPath.Evaluate(pathFilters, root, t, false);
return JPath.Evaluate(pathFilters, root, t, null);
}
return CollectionUtils.ArrayEmpty<JToken>();
}
public override bool IsMatch(JToken root, JToken t)
public override bool IsMatch(JToken root, JToken t, JsonSelectSettings? settings)
{
if (Operator == QueryOperator.Exists)
{
@@ -131,7 +130,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
JToken leftResult = leftResults.Current;
foreach (JToken rightResult in rightResults)
{
if (MatchTokens(leftResult, rightResult))
if (MatchTokens(leftResult, rightResult, settings))
{
return true;
}
@@ -143,14 +142,14 @@ namespace Newtonsoft.Json.Linq.JsonPath
return false;
}
private bool MatchTokens(JToken leftResult, JToken rightResult)
private bool MatchTokens(JToken leftResult, JToken rightResult, JsonSelectSettings? settings)
{
if (leftResult is JValue leftValue && rightResult is JValue rightValue)
{
switch (Operator)
{
case QueryOperator.RegexEquals:
if (RegexEquals(leftValue, rightValue, SingleMatchTimeout))
if (RegexEquals(leftValue, rightValue, settings))
{
return true;
}
@@ -222,9 +221,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
return false;
}
private static bool RegexEquals(JValue input,
JValue pattern,
TimeSpan? regexMatchTimeout = default)
private static bool RegexEquals(JValue input, JValue pattern, JsonSelectSettings? settings)
{
if (input.Type != JTokenType.String || pattern.Type != JTokenType.String)
{
@@ -238,7 +235,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1);
#if HAVE_REGEX_TIMEOUTS
var timeout = regexMatchTimeout ?? Regex.InfiniteMatchTimeout;
TimeSpan timeout = settings?.RegexMatchTimeout ?? Regex.InfiniteMatchTimeout;
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText), timeout);
#else
return Regex.IsMatch((string)input.Value!, patternText, MiscellaneousUtils.GetRegexOptions(optionsText));
@@ -12,13 +12,13 @@ namespace Newtonsoft.Json.Linq.JsonPath
Expression = expression;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
foreach (JToken v in t)
{
if (Expression.IsMatch(root, v))
if (Expression.IsMatch(root, v, settings))
{
yield return v;
}
@@ -12,7 +12,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
Expression = expression;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken t in current)
{
@@ -20,7 +20,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
foreach (JToken d in c.DescendantsAndSelf())
{
if (Expression.IsMatch(root, d))
if (Expression.IsMatch(root, d, settings))
{
yield return d;
}
@@ -28,7 +28,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
}
else
{
if (Expression.IsMatch(root, t))
if (Expression.IsMatch(root, t, settings))
{
yield return t;
}
@@ -10,7 +10,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
{
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
return new[] { root };
}
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
Name = name;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken c in current)
{
@@ -11,7 +11,7 @@ namespace Newtonsoft.Json.Linq.JsonPath
_names = names;
}
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, bool errorWhenNoMatch)
public override IEnumerable<JToken> ExecuteFilter(JToken root, IEnumerable<JToken> current, JsonSelectSettings? settings)
{
foreach (JToken c in current)
{
@@ -0,0 +1,28 @@
using System;
namespace Newtonsoft.Json.Linq
{
/// <summary>
/// Specifies the settings used when selecting JSON.
/// </summary>
public class JsonSelectSettings
{
#if HAVE_REGEX_TIMEOUTS
/// <summary>
/// Gets or sets a timeout that will be used when executing regular expressions.
/// </summary>
/// <value>The timeout that will be used when executing regular expressions.</value>
public TimeSpan? RegexMatchTimeout { get; set; }
#endif
/// <summary>
/// Gets or sets a flag that indicates whether an error should be thrown if
/// no tokens are found when evaluating part of the expression.
/// </summary>
/// <value>
/// A flag that indicates whether an error should be thrown if
/// no tokens are found when evaluating part of the expression.
/// </value>
public bool ErrorWhenNoMatch { get; set; }
}
}
+1 -1
View File
@@ -45,7 +45,7 @@
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net45'">
<AssemblyTitle>Json.NET</AssemblyTitle>
<DefineConstants>HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CAS;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;$(AdditionalConstants)</DefineConstants>
<DefineConstants>HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;HAVE_CAS;HAVE_CHAR_TO_LOWER_WITH_CULTURE;HAVE_CHAR_TO_STRING_WITH_CULTURE;HAVE_COM_ATTRIBUTES;HAVE_COMPONENT_MODEL;HAVE_CONCURRENT_COLLECTIONS;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DB_NULL_TYPE_CODE;HAVE_DYNAMIC;HAVE_EMPTY_TYPES;HAVE_ENTITY_FRAMEWORK;HAVE_EXPRESSIONS;HAVE_FAST_REVERSE;HAVE_FSHARP_TYPES;HAVE_FULL_REFLECTION;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_ICLONEABLE;HAVE_ICONVERTIBLE;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_MEMORY_BARRIER;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_EMIT;HAVE_REGEX_TIMEOUTS;HAVE_SECURITY_SAFE_CRITICAL_ATTRIBUTE;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STREAM_READER_WRITER_CLOSE;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TRACE_WRITER;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_XML_DOCUMENT;HAVE_XML_DOCUMENT_TYPE;HAVE_CONCURRENT_DICTIONARY;$(AdditionalConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net40'">
<AssemblyTitle>Json.NET .NET 4.0</AssemblyTitle>
@@ -165,11 +165,6 @@ namespace Newtonsoft.Json.Utilities
case 'x':
options |= RegexOptions.ExplicitCapture;
break;
#if HAVE_REGEX_TIMEOUTS && (NETSTANDARD1_3 || NETSTANDARD2_0)
case 'c':
options |= RegexOptions.Compiled;
break;
#endif
}
}