diff --git a/Src/Directory.Build.props b/Src/Directory.Build.props index 11ecc418..cfaf3745 100644 --- a/Src/Directory.Build.props +++ b/Src/Directory.Build.props @@ -22,5 +22,7 @@ 4.4.0 2.3.1 2.3.1 + 32.0.2 + 5.1.0 diff --git a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs index 69c1a815..6b3a512e 100644 --- a/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs +++ b/Src/Newtonsoft.Json.Tests/Linq/JsonPath/JPathExecuteTests.cs @@ -31,6 +31,13 @@ using System.Numerics; #endif 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; using Test = Xunit.FactAttribute; @@ -44,7 +51,6 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; - #endif namespace Newtonsoft.Json.Tests.Linq.JsonPath @@ -71,6 +77,75 @@ namespace Newtonsoft.Json.Tests.Linq.JsonPath Assert.AreEqual(jObj, dd); } +#if HAVE_REGEX_TIMEOUTS + [Test] + public void BacktrackingRegex_SingleMatch_TimeoutRespected() + { + var RegexBacktrackingPattern = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + + 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))); + } + + Xunit.Assert.Throws(() => + { + var tokens = regexBacktrackingData.SelectTokens( + $"[?(@.b =~ /{RegexBacktrackingPattern}/)]", + errorWhenNoMatch: false, + singleRegexMatchTimeout: TimeSpan.FromSeconds(.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 = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + var jpathExpression = $"[?(@.b =~ /{RegexBacktrackingPattern}/c)]"; + + var exceptionThrow = Xunit.Assert.ThrowsAny(() => + { + 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 = "(?(.*?))[|].*(?(.*?))[|].*(?(.*?))[|].*(?[1-3])[|].*(?(.*?))[|].*[|].*[|].*(?(.*?))[|].*[|].*(?(.*?))[|].*(?(.*))"; + 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(cts.Token); + var finishedTask = await Task.WhenAny(selectTokenTask, ctts.Task); + Assert.AreEqual(finishedTask, ctts.Task); + } +#endif + [Test] public void GreaterThanWithIntegerParameterAndStringValue() { diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 2d619e32..7989fe9f 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -2,7 +2,7 @@ net46;net40;net35;net20;net5.0;netcoreapp3.1;netcoreapp2.1 $(TestFrameworks) - 8.0 + 9.0 1.0 James Newton-King Newtonsoft @@ -76,6 +76,7 @@ + Json.NET Tests .NET 4.0 @@ -121,11 +122,14 @@ + + + Json.NET Tests .NET Standard 2.0 .NETStandard,Version=v2.0 - NETSTANDARD2_0;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;$(AdditionalConstants) + NETSTANDARD2_0;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -142,11 +146,13 @@ + + Json.NET Tests .NET Standard 1.3 .NETStandard,Version=v1.3 - NETSTANDARD1_3;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;$(AdditionalConstants) + NETSTANDARD1_3;DNXCORE50;PORTABLE;HAVE_BENCHMARKS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -162,10 +168,13 @@ + + + Json.NET Tests .NET Standard 1.0 .NETStandard,Version=v1.0 - NETSTANDARD1_0;DNXCORE50;PORTABLE;$(AdditionalConstants) + NETSTANDARD1_0;DNXCORE50;PORTABLE;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JToken.cs b/Src/Newtonsoft.Json/Linq/JToken.cs index 79aa9124..252f846c 100644 --- a/Src/Newtonsoft.Json/Linq/JToken.cs +++ b/Src/Newtonsoft.Json/Linq/JToken.cs @@ -44,6 +44,7 @@ using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json.Utilities.LinqBridge; #else using System.Linq; +using System.Threading; #endif namespace Newtonsoft.Json.Linq @@ -485,7 +486,7 @@ namespace Newtonsoft.Json.Linq return (Array.IndexOf(validTypes, o.Type) != -1) || (nullable && (o.Type == JTokenType.Null || o.Type == JTokenType.Undefined)); } -#region Cast from operators + #region Cast from operators /// /// Performs an explicit conversion from to . /// @@ -1497,9 +1498,9 @@ namespace Newtonsoft.Json.Linq return ConvertUtils.ToBigInteger(v.Value); } #endif -#endregion + #endregion -#region Cast to operators + #region Cast to operators /// /// Performs an implicit conversion from to . /// @@ -1863,7 +1864,7 @@ namespace Newtonsoft.Json.Linq { return new JValue(value); } -#endregion + #endregion IEnumerator IEnumerable.GetEnumerator() { @@ -2336,6 +2337,35 @@ namespace Newtonsoft.Json.Linq return token; } +#if HAVE_REGEX_TIMEOUTS + /// + /// Selects a using a JSONPath expression. Selects the token that matches the object path. + /// + /// + /// A that contains a JSONPath expression. + /// + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// the time after which a single call to regex.ismatch must complete, default is forever + /// A . + public JToken? SelectToken(string path, bool errorWhenNoMatch, TimeSpan? singleMatchTimeout = default) + { + JPath p = new JPath(path, singleMatchTimeout); + + 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; + } +#endif + /// /// Selects a collection of elements using a JSONPath expression. /// @@ -2358,10 +2388,51 @@ namespace Newtonsoft.Json.Linq /// An of that contains the selected elements. public IEnumerable SelectTokens(string path, bool errorWhenNoMatch) { - JPath p = new JPath(path); + var p = new JPath(path); return p.Evaluate(this, this, errorWhenNoMatch); } +#if HAVE_REGEX_TIMEOUTS + /// + /// Selects a collection of elements using a JSONPath expression. + /// + /// + /// A that contains a JSONPath expression. + /// + /// A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + /// + /// for every token that matches a jpath, the time a regex is permitted to run + /// against that token before timeout + /// + /// + /// 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) + /// + /// if single call timeout exceeded" + /// An of that contains the selected elements. + public IEnumerable SelectTokens(string path, + bool errorWhenNoMatch, + TimeSpan? singleRegexMatchTimeout = default, + TimeSpan? globalRegexMatchTimeout = default) + { + 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; + } + } +#endif + #if HAVE_DYNAMIC /// /// Returns the responsible for binding operations performed on this object. diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs index f18fb6c9..d5bc4e89 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/JPath.cs @@ -27,6 +27,8 @@ 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 @@ -37,14 +39,16 @@ namespace Newtonsoft.Json.Linq.JsonPath private readonly string _expression; public List Filters { get; } + public readonly TimeSpan? RegexSingleMatchTimeout = default; private int _currentIndex; - public JPath(string expression) + public JPath(string expression, TimeSpan? regexSingleMatchTimeout = default) { ValidationUtils.ArgumentNotNull(expression, nameof(expression)); _expression = expression; Filters = new List(); + RegexSingleMatchTimeout = regexSingleMatchTimeout; ParseMain(); } @@ -509,7 +513,8 @@ namespace Newtonsoft.Json.Linq.JsonPath right = ParseSide(); } - BooleanQueryExpression booleanExpression = new BooleanQueryExpression(op, left, right); + + var booleanExpression = new BooleanQueryExpression(op, left, right, RegexSingleMatchTimeout); if (_expression[_currentIndex] == ')') { diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs index f7bf1d5f..f9aa84d1 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -4,6 +4,7 @@ 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 @@ -83,11 +84,17 @@ 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) : base(@operator) + public BooleanQueryExpression(QueryOperator @operator, + object left, + object? right, + TimeSpan? regexSingleMatchTimeout = default) + : base(@operator) { Left = left; Right = right; + SingleMatchTimeout = regexSingleMatchTimeout; } private IEnumerable GetResult(JToken root, JToken t, object? o) @@ -143,7 +150,7 @@ namespace Newtonsoft.Json.Linq.JsonPath switch (Operator) { case QueryOperator.RegexEquals: - if (RegexEquals(leftValue, rightValue)) + if (RegexEquals(leftValue, rightValue, SingleMatchTimeout)) { return true; } @@ -215,7 +222,9 @@ namespace Newtonsoft.Json.Linq.JsonPath return false; } - private static bool RegexEquals(JValue input, JValue pattern) + private static bool RegexEquals(JValue input, + JValue pattern, + TimeSpan? regexMatchTimeout = default) { if (input.Type != JTokenType.String || pattern.Type != JTokenType.String) { @@ -228,7 +237,12 @@ namespace Newtonsoft.Json.Linq.JsonPath string patternText = regexText.Substring(1, patternOptionDelimiterIndex - 1); string optionsText = regexText.Substring(patternOptionDelimiterIndex + 1); +#if HAVE_REGEX_TIMEOUTS + var timeout = 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)); +#endif } internal static bool EqualsWithStringCoercion(JValue value, JValue queryValue) diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index ea973f48..f6cc6cc7 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -66,7 +66,7 @@ Json.NET .NET Standard 1.0 - NETSTANDARD1_0;PORTABLE;HAVE_ASYNC;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;$(AdditionalConstants) + NETSTANDARD1_0;PORTABLE;HAVE_ASYNC;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) @@ -74,10 +74,10 @@ Json.NET .NET Standard 1.3 - NETSTANDARD1_3;PORTABLE;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_SERIALIZATION;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_XML_DOCUMENT;HAVE_CONCURRENT_DICTIONARY;HAVE_ICONVERTIBLE;$(AdditionalConstants) + NETSTANDARD1_3;PORTABLE;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_SERIALIZATION;HAVE_COVARIANT_GENERICS;HAVE_DATA_CONTRACTS;HAVE_DATE_TIME_OFFSET;HAVE_DYNAMIC;HAVE_EXPRESSIONS;HAVE_FSHARP_TYPES;HAVE_GUID_TRY_PARSE;HAVE_HASH_SET;HAVE_IGNORE_DATA_MEMBER_ATTRIBUTE;HAVE_INOTIFY_COLLECTION_CHANGED;HAVE_INOTIFY_PROPERTY_CHANGING;HAVE_ISET;HAVE_LINQ;HAVE_METHOD_IMPL_ATTRIBUTE;HAVE_NON_SERIALIZED_ATTRIBUTE;HAVE_READ_ONLY_COLLECTIONS;HAVE_REFLECTION_BINDER;HAVE_SERIALIZATION_BINDER_BIND_TO_NAME;HAVE_STRING_JOIN_WITH_ENUMERABLE;HAVE_TIME_SPAN_PARSE_WITH_CULTURE;HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE;HAVE_TIME_ZONE_INFO;HAVE_TYPE_DESCRIPTOR;HAVE_UNICODE_SURROGATE_DETECTION;HAVE_VARIANT_TYPE_PARAMETERS;HAVE_VERSION_TRY_PARSE;HAVE_XLINQ;HAVE_OBSOLETE_FORMATTER_ASSEMBLY_STYLE;HAVE_XML_DOCUMENT;HAVE_CONCURRENT_DICTIONARY;HAVE_ICONVERTIBLE;HAVE_REGEX_TIMEOUTS;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) Json.NET .NET Standard 2.0 - NETSTANDARD2_0;HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;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_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) + NETSTANDARD2_0;HAVE_ADO_NET;HAVE_APP_DOMAIN;HAVE_ASYNC;HAVE_BIG_INTEGER;HAVE_BINARY_FORMATTER;HAVE_BINARY_SERIALIZATION;HAVE_BINARY_EXCEPTION_SERIALIZATION;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_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;HAVE_REGEX_TIMEOUTS;$(AdditionalConstants) \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs index 137ec445..d61ea5d7 100644 --- a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs @@ -165,6 +165,11 @@ 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 } }