Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<!-- These packages should only be directly included for TFM net48. -->
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
</ItemGroup>

<ItemGroup>
<!-- Do not raise the FluentAssertions version above 7.x due to license changes -->
<!-- WARNING: Do not raise the FluentAssertions version above 7.x due to license changes -->
<PackageVersion Include="FluentAssertions" Version="7.2.0" />
<!-- WARNING: Do not raise the JsonSchema.Net version above 8.0.5 due to license changes -->
<PackageVersion Include="JsonSchema.Net" Version="7.1.2" />
<!-- WARNING: Do not raise the Yaml2JsonNode version above 2.4.0 due to license changes -->
<PackageVersion Include="Yaml2JsonNode" Version="2.2.0" />

<!-- WARNING:
Moq versions 4.20.0–4.20.69 should be avoided due to the inclusion of the SponsorLink analyzer,
Expand All @@ -17,21 +24,18 @@
-->
<!--<PackageVersion Include="Moq" Version="4.18.2" />-->

<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.IO.Compression" Version="4.3.0" Condition="'$(TargetFramework)' == 'net48'" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<!-- System.Text.Json is a transitive dependency of Yaml2JsonNode for netstandard2.0. Pinning to 8.0.5 for CVE-2024-43485, but can be removed on update of Yaml2JsonNode -->
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageVersion Include="Yaml2JsonNode" Version="2.1.1" />
<PackageVersion Include="YamlDotNet" Version="16.1.3" />

<PackageVersion Include="PolySharp" Version="1.15.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.7" />
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Text.Encodings.Web" Version="10.0.7" />
<PackageVersion Include="System.Text.Json" Version="10.0.7" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="YamlDotNet" Version="17.1.0" />
</ItemGroup>
</Project>
9 changes: 6 additions & 3 deletions src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="System.IO.Compression" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="System.Text.Encodings.Web" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="YamlDotNet" />
<PackageReference Include="System.IO.Compression" Condition="'$(TargetFramework)' == 'net48'" />
</ItemGroup>

<ItemGroup>
Expand Down
127 changes: 57 additions & 70 deletions src/PAModel/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,93 @@
},
"System.Text.Encodings.Web": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "WUH+viO8VDG8NpFKvOBwpeyKUiPOMz3kQpA6AKCD4b2NG1pBhyC4AwTb357iZmTxZDnkM4IsFnvzN8W8OKmsHg==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
"System.Buffers": "4.6.1",
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Text.Json": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
"requested": "[10.0.7, )",
"resolved": "10.0.7",
"contentHash": "F8Pu2QLUMeniVbtiyk7n7LCfFYxlcJ8ASaSwglJyq6dxa34iCQrikQszsgJClIJWuSWjcyhKkV7daAzYJqeVwA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "8.0.0",
"System.Threading.Tasks.Extensions": "4.5.4",
"System.ValueTuple": "4.5.0"
"Microsoft.Bcl.AsyncInterfaces": "10.0.7",
"System.Buffers": "4.6.1",
"System.IO.Pipelines": "10.0.7",
"System.Memory": "4.6.3",
"System.Runtime.CompilerServices.Unsafe": "6.1.2",
"System.Text.Encodings.Web": "10.0.7",
"System.Threading.Tasks.Extensions": "4.6.3",
"System.ValueTuple": "4.6.2"
}
},
"YamlDotNet": {
"type": "Direct",
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
"requested": "[17.1.0, )",
"resolved": "17.1.0",
"contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==",
"resolved": "10.0.7",
"contentHash": "g0Xp9A+B8jCf5pNIIhFOQXPJkte3D87shfTLY+ylwfSh22U5oQH6tvvmcUuqJvt/wtwKk0WdNp2OGEczHJlJdg==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
"resolved": "4.6.1",
"contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw=="
},
"System.IO.Pipelines": {
"type": "Transitive",
"resolved": "10.0.7",
"contentHash": "LTxXYYKmRhPKWveYmfzuRTUnzsfY7CN+WOq6aTRgYE9vJ8BUvIWPCaSx4HxqBwXViTPSjR9cHDOVuVPuZGRR/Q==",
"dependencies": {
"System.Buffers": "4.6.1",
"System.Memory": "4.6.3",
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
"resolved": "4.6.1",
"contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.ValueTuple": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ=="
"resolved": "4.6.2",
"contentHash": "yQgmjfFximrNm9LIV3mL6T5MzjeC+epeE5rl4hXxAlYmxby7RM1dPSkIKXk9HNkl6G54h2JHOmLD46+Pey+IRg=="
},
"System.Memory": {
"type": "CentralTransitive",
"requested": "[4.5.5, )",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"requested": "[4.6.3, )",
"resolved": "4.6.3",
"contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
"System.Buffers": "4.6.1",
"System.Numerics.Vectors": "4.6.1",
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"System.Threading.Tasks.Extensions": {
"type": "CentralTransitive",
"requested": "[4.5.4, )",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"requested": "[4.6.3, )",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
}
},
Expand All @@ -102,23 +113,11 @@
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.Text.Encodings.Web": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
},
"System.Text.Json": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg=="
},
"YamlDotNet": {
"type": "Direct",
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
"requested": "[17.1.0, )",
"resolved": "17.1.0",
"contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA=="
}
},
"net8.0": {
Expand All @@ -128,23 +127,11 @@
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"System.Text.Encodings.Web": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ=="
},
"System.Text.Json": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg=="
},
"YamlDotNet": {
"type": "Direct",
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
"requested": "[17.1.0, )",
"resolved": "17.1.0",
"contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA=="
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/PAModelTests/PAModelTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="YamlDotNet" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,47 @@ public void SerializeComponentCustomPropertyUnionPropertyOrder()
Default: =true
""".Replace("\r\n", "\n"));
}

[TestMethod]
public void DeserializeRespectsConfiguredMaximumRecursion()
{
// Arrange: build a deeply-nested mapping that exceeds the configured recursion limit.
var options = new PaYamlSerializerOptions { MaximumRecursion = 5 };
var yaml = BuildNestedMappingYaml(depth: 20);

// Act
Action act = () => PaYamlSerializer.Deserialize<Dictionary<string, object>>(yaml, options);

// Assert: the exception is wrapped as PersistenceLibraryException with the
// YamlDotNet MaximumRecursionLevelReachedException as the inner exception.
act.Should().Throw<PersistenceLibraryException>()
.WithErrorCode(PersistenceErrorCode.MaximumRecursionLevelReached)
.WithInnerException<YamlDotNet.Core.MaximumRecursionLevelReachedException>();
}

[TestMethod]
[DataRow(null)]
[DataRow(55)]
public void DeserializeSucceedsWhenMaximumRecursionRaisedOrNull(int? maxRecursion)
{
// Arrange: same depth-20 input that fails with default recursion, raised limit.
var options = new PaYamlSerializerOptions { MaximumRecursion = maxRecursion };
var yaml = BuildNestedMappingYaml(depth: 20);

// Act
FluentActions.Invoking(() => PaYamlSerializer.Deserialize<Dictionary<string, object>>(yaml, options))
.Should().NotThrow()
.Which.Should().NotBeNull();
}

private static string BuildNestedMappingYaml(int depth)
{
var sb = new System.Text.StringBuilder();
for (var i = 0; i < depth; i++)
{
sb.Append(new string(' ', i * 2)).Append("k:").AppendLine();
}
sb.Append(new string(' ', depth * 2)).Append("k: leaf").AppendLine();
return sb.ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public class PaYamlSerializationContext : IDisposable
public PaYamlSerializationContext(PaYamlSerializerOptions options)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
if (Options.MaximumRecursion.HasValue && Options.MaximumRecursion.Value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(options), "MaximumRecursion must be an integer greater than 0 or null.");
}
}

/// <summary>
Expand All @@ -27,6 +31,11 @@ internal void ApplyToDeserializerBuilder(DeserializerBuilder builder)
.IgnoreFields()
;

if (Options.MaximumRecursion.HasValue)
{
builder.WithMaximumRecursion(Options.MaximumRecursion.Value);
}

AddTypeConverters(builder);
Options.AdditionalDeserializerConfiguration?.Invoke(builder);
}
Expand Down
4 changes: 3 additions & 1 deletion src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ private static void WriteTextWriter<TValue>(TextWriter writer, in TValue? value,
catch (YamlException ex)
{
var errorCode = PersistenceErrorCode.YamlInvalidSyntax;
if (ex.InnerException is ArgumentException && ex.InnerException.Message.Contains("An item with the same key has already been added"))
if (ex is MaximumRecursionLevelReachedException)
errorCode = PersistenceErrorCode.MaximumRecursionLevelReached;
else if (ex.InnerException is ArgumentException && ex.InnerException.Message.Contains("An item with the same key has already been added"))
Comment thread
joem-msft marked this conversation as resolved.
errorCode = PersistenceErrorCode.DuplicateNameInSequence;

throw PersistenceLibraryException.FromYamlException(ex, errorCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ public record PaYamlSerializerOptions

public string NewLine { get; init; } = "\n";

/// <summary>
/// Maximum recursion depth allowed during deserialization.
/// Bounds pathological/untrusted YAML input.
/// </summary>
public int? MaximumRecursion { get; init; }

public PFxExpressionYamlFormattingOptions PFxExpressionYamlFormatting { get; init; } = new();

public Action<DeserializerBuilder>? AdditionalDeserializerConfiguration { get; init; }
Expand Down
6 changes: 6 additions & 0 deletions src/Persistence/PersistenceErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public enum PersistenceErrorCode
// 3xxx - Deserialization errors
DeserializationError = 3000,
YamlInvalidSyntax = 3001,
/// <summary>
/// Indicates that the deserializer exceeded the maximum recursion depth when processing the YAML document.
/// This can be caused by excessively deep or circular references in the object graph represented by the YAML.
/// The default maximum recursion depth can be configured through <see cref="PaYamlSerializerOptions.MaximumRecursion"/>.
/// </summary>
MaximumRecursionLevelReached = 3002,
YamlInvalidSchema = 3101,
EditorStateJsonEmptyOrNull = 3102,
InvalidEditorStateJson = 3300,
Expand Down
Loading
Loading