Skip to content

Commit 7a95a31

Browse files
authored
Merge pull request #423 from CycloneDX/maintenance
Update dependencies and fix thread-safety in JSON schema validation
2 parents 2aac3ce + 28f9471 commit 7a95a31

File tree

37 files changed

+173
-168
lines changed

37 files changed

+173
-168
lines changed

src/CycloneDX.Core/CycloneDX.Core.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
11+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103">
1212
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1313
<PrivateAssets>all</PrivateAssets>
1414
</PackageReference>
15-
<PackageReference Include="JsonSchema.Net" Version="5.3.1" />
16-
<PackageReference Include="protobuf-net" Version="3.2.45" />
17-
<PackageReference Include="protobuf-net.BuildTools" Version="3.2.12" PrivateAssets="all" IncludeAssets="runtime;build;native;contentfiles;analyzers;buildtransitive" />
15+
<PackageReference Include="JsonSchema.Net" Version="9.1.1" />
16+
<PackageReference Include="protobuf-net" Version="3.2.56" />
17+
<PackageReference Include="protobuf-net.BuildTools" Version="3.2.52" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
1818
</ItemGroup>
1919

2020
<!--The below packages are natively provided by the framework hence are not need for the frameworks which they are included in. What is included can be checked via https://apisof.net/-->
2121
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
22-
<PackageReference Include="System.Text.Json" Version="8.0.5" />
22+
<PackageReference Include="System.Text.Json" Version="10.0.3" />
2323
</ItemGroup>
2424

2525
<ItemGroup>

src/CycloneDX.Core/Json/Validator.cs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ namespace CycloneDX.Json
3131
/// </summary>
3232
public static class Validator
3333
{
34+
private static readonly Dictionary<SpecificationVersion, JsonSchema> _bomSchemas = new Dictionary<SpecificationVersion, JsonSchema>();
35+
3436
static Validator()
3537
{
36-
// I think the global schema registry is not thread safe
37-
// well, I'm pretty sure, it's the only thing I can think of that would explain the sporadic test failures
38-
// might as well just do it once on initialisation
38+
// The global schema registry is not thread safe, and JsonSchema.FromText
39+
// registers schemas internally. Pre-load everything once at initialisation
40+
// to avoid concurrent registration conflicts during parallel test execution.
3941
var assembly = typeof(Validator).GetTypeInfo().Assembly;
4042
using (var spdxStream = assembly.GetManifestResourceStream("CycloneDX.Core.Schemas.spdx.schema.json"))
4143
using (var spdxStreamReader = new StreamReader(spdxStream))
@@ -55,6 +57,26 @@ static Validator()
5557
var cryptoDefsSchema = JsonSchema.FromText(cryptoDefsStreamReader.ReadToEnd());
5658
SchemaRegistry.Global.Register(new Uri("http://cyclonedx.org/schema/cryptography-defs.schema.json"), cryptoDefsSchema);
5759
}
60+
61+
// Pre-load all BOM schemas (v1.2+) so they are never parsed concurrently
62+
var jsonVersions = new[]
63+
{
64+
SpecificationVersion.v1_2,
65+
SpecificationVersion.v1_3,
66+
SpecificationVersion.v1_4,
67+
SpecificationVersion.v1_5,
68+
SpecificationVersion.v1_6,
69+
SpecificationVersion.v1_7,
70+
};
71+
foreach (var version in jsonVersions)
72+
{
73+
var versionString = SchemaVersionResourceFilenameString(version);
74+
using (var stream = assembly.GetManifestResourceStream($"CycloneDX.Core.Schemas.bom-{versionString}.schema.json"))
75+
using (var reader = new StreamReader(stream))
76+
{
77+
_bomSchemas[version] = JsonSchema.FromText(reader.ReadToEnd());
78+
}
79+
}
5880
}
5981

6082
/// <summary>
@@ -71,14 +93,9 @@ public static async Task<ValidationResult> ValidateAsync(Stream jsonStream, Spec
7193
}
7294

7395
var schemaVersionString = SchemaVersionResourceFilenameString(specificationVersion);
74-
var assembly = typeof(Validator).GetTypeInfo().Assembly;
75-
76-
using (var schemaStream = assembly.GetManifestResourceStream($"CycloneDX.Core.Schemas.bom-{schemaVersionString}.schema.json"))
77-
{
78-
var jsonSchema = await JsonSchema.FromStream(schemaStream).ConfigureAwait(false);
79-
var jsonDocument = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
80-
return Validate(jsonSchema, jsonDocument, schemaVersionString);
81-
}
96+
var jsonSchema = _bomSchemas[specificationVersion];
97+
var jsonDocument = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
98+
return Validate(jsonSchema, jsonDocument, schemaVersionString);
8299
}
83100

84101
/// <summary>
@@ -162,25 +179,19 @@ public static ValidationResult Validate(string jsonString, SpecificationVersion
162179
}
163180

164181
var schemaVersionString = SchemaVersionResourceFilenameString(specificationVersion);
165-
var assembly = typeof(Validator).GetTypeInfo().Assembly;
166-
167-
using (var schemaStream = assembly.GetManifestResourceStream($"CycloneDX.Core.Schemas.bom-{schemaVersionString}.schema.json"))
168-
using (var schemaStreamReader = new StreamReader(schemaStream))
182+
var jsonSchema = _bomSchemas[specificationVersion];
183+
try
169184
{
170-
var jsonSchema = JsonSchema.FromText(schemaStreamReader.ReadToEnd());
171-
try
172-
{
173-
var jsonDocument = JsonDocument.Parse(jsonString);
174-
return Validate(jsonSchema, jsonDocument, schemaVersionString);
175-
}
176-
catch (JsonException exc)
185+
var jsonDocument = JsonDocument.Parse(jsonString);
186+
return Validate(jsonSchema, jsonDocument, schemaVersionString);
187+
}
188+
catch (JsonException exc)
189+
{
190+
return new ValidationResult
177191
{
178-
return new ValidationResult
179-
{
180-
Valid = false,
181-
Messages = new List<string> { exc.Message }
182-
};
183-
}
192+
Valid = false,
193+
Messages = new List<string> { exc.Message }
194+
};
184195
}
185196
}
186197

@@ -232,7 +243,7 @@ private static ValidationResult Validate(JsonSchema schema, JsonDocument jsonDoc
232243
continue;
233244
}
234245

235-
if (detail.HasErrors)
246+
if (detail.Errors != null)
236247
{
237248
foreach (var error in detail.Errors)
238249
{

src/CycloneDX.Core/Models/AttachedText.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ namespace CycloneDX.Models
2424
public class AttachedText
2525
{
2626
[XmlAttribute("content-type")]
27+
#pragma warning disable PBN0020 // DefaultValue would change protobuf serialization behavior
2728
[ProtoMember(1)]
2829
public string ContentType { get; set; } = "text/plain";
30+
#pragma warning restore PBN0020
2931

3032
[XmlAttribute("encoding")]
3133
[ProtoMember(2)]

src/CycloneDX.Core/Models/Crypto/RelatedCryptoMaterialProperties.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,45 @@ public class RelatedCryptoMaterialProperties
4343
[ProtoMember(4)]
4444
public string AlgorithmRef { get; set; }
4545

46+
private DateTime? _creationDate;
4647
[XmlElement("creationDate")]
4748
[ProtoMember(5)]
48-
public DateTime CreationDate { get; set; }
49+
public DateTime? CreationDate
50+
{
51+
get => _creationDate;
52+
set { _creationDate = BomUtils.UtcifyDateTime(value); }
53+
}
54+
public bool ShouldSerializeCreationDate() { return CreationDate != null; }
4955

56+
private DateTime? _activationDate;
5057
[XmlElement("activationDate")]
5158
[ProtoMember(6)]
52-
public DateTime ActivationDate { get; set; }
59+
public DateTime? ActivationDate
60+
{
61+
get => _activationDate;
62+
set { _activationDate = BomUtils.UtcifyDateTime(value); }
63+
}
64+
public bool ShouldSerializeActivationDate() { return ActivationDate != null; }
5365

66+
private DateTime? _updateDate;
5467
[XmlElement("updateDate")]
5568
[ProtoMember(7)]
56-
public DateTime UpdateDate { get; set; }
69+
public DateTime? UpdateDate
70+
{
71+
get => _updateDate;
72+
set { _updateDate = BomUtils.UtcifyDateTime(value); }
73+
}
74+
public bool ShouldSerializeUpdateDate() { return UpdateDate != null; }
5775

76+
private DateTime? _expirationDate;
5877
[XmlElement("expirationDate")]
5978
[ProtoMember(8)]
60-
public DateTime ExpirationDate { get; set; }
79+
public DateTime? ExpirationDate
80+
{
81+
get => _expirationDate;
82+
set { _expirationDate = BomUtils.UtcifyDateTime(value); }
83+
}
84+
public bool ShouldSerializeExpirationDate() { return ExpirationDate != null; }
6185

6286
[XmlElement("value")]
6387
[ProtoMember(9)]

src/CycloneDX.Core/Models/Swid.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ public class Swid
3434
public string Name { get; set; }
3535

3636
[XmlAttribute("version")]
37+
#pragma warning disable PBN0020 // DefaultValue would change protobuf serialization behavior
3738
[ProtoMember(3)]
3839
public string Version { get; set; } = "0.0";
40+
#pragma warning restore PBN0020
3941

4042
[XmlAttribute("tagVersion")]
4143
[ProtoMember(4)]

src/CycloneDX.Spdx.Interop/CycloneDX.Spdx.Interop.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</ItemGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
17+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103">
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
<PrivateAssets>all</PrivateAssets>
2020
</PackageReference>

src/CycloneDX.Spdx/CycloneDX.Spdx.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
11+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103">
1212
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1313
<PrivateAssets>all</PrivateAssets>
1414
</PackageReference>
15-
<PackageReference Include="JsonSchema.Net" Version="5.3.1" />
15+
<PackageReference Include="JsonSchema.Net" Version="9.1.1" />
1616
</ItemGroup>
1717

1818
<!--The below packages are natively provided by the framework hence are not need for the frameworks which they are included in. What is included can be checked via https://apisof.net/-->
1919
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
20-
<PackageReference Include="System.Text.Json" Version="8.0.5" />
20+
<PackageReference Include="System.Text.Json" Version="10.0.3" />
2121
</ItemGroup>
2222

2323
<ItemGroup>
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<PropertyGroup />
4-
<ItemGroup>
5-
<EmbeddedResource Update="Schemas\spdx-2.3.schema.xsd">
6-
<SubType>Designer</SubType>
7-
</EmbeddedResource>
8-
</ItemGroup>
4+
<ItemGroup />
95
</Project>

src/CycloneDX.Spdx/Validation/JsonValidator.cs

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ namespace CycloneDX.Spdx.Validation
2828
{
2929
public static class JsonValidator
3030
{
31+
private static readonly JsonSchema _spdxSchema;
32+
33+
static JsonValidator()
34+
{
35+
// Pre-load the SPDX schema once to avoid concurrent JsonSchema.FromText
36+
// calls registering schemas in the global registry in parallel.
37+
var assembly = typeof(JsonValidator).GetTypeInfo().Assembly;
38+
using (var schemaStream = assembly.GetManifestResourceStream("CycloneDX.Spdx.Schemas.spdx-2.3.schema.json"))
39+
using (var schemaStreamReader = new StreamReader(schemaStream))
40+
{
41+
_spdxSchema = JsonSchema.FromText(schemaStreamReader.ReadToEnd());
42+
}
43+
}
44+
3145
/// <summary>
3246
/// Validate the stream contents represent a valid SPDX JSON document.
3347
/// </summary>
@@ -36,42 +50,29 @@ public static class JsonValidator
3650
/// <returns></returns>
3751
public static async Task<ValidationResult> ValidateAsync(Stream jsonStream)
3852
{
39-
var assembly = typeof(JsonValidator).GetTypeInfo().Assembly;
40-
41-
using (var schemaStream = assembly.GetManifestResourceStream($"CycloneDX.Spdx.Schemas.spdx-2.3.schema.json"))
42-
{
43-
var jsonSchema = await JsonSchema.FromStream(schemaStream).ConfigureAwait(false);
44-
var jsonDocument = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
45-
return Validate(jsonSchema, jsonDocument);
46-
}
53+
var jsonDocument = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
54+
return Validate(_spdxSchema, jsonDocument);
4755
}
48-
56+
4957
/// <summary>
5058
/// Validate the string contents represent a valid SPDX JSON document.
5159
/// </summary>
5260
/// <param name="jsonString"></param>
5361
/// <returns></returns>
5462
public static ValidationResult Validate(string jsonString)
5563
{
56-
var assembly = typeof(JsonValidator).GetTypeInfo().Assembly;
57-
58-
using (var schemaStream = assembly.GetManifestResourceStream($"CycloneDX.Spdx.Schemas.spdx-2.3.schema.json"))
59-
using (var schemaStreamReader = new StreamReader(schemaStream))
64+
try
6065
{
61-
var jsonSchema = JsonSchema.FromText(schemaStreamReader.ReadToEnd());
62-
try
63-
{
64-
var jsonDocument = JsonDocument.Parse(jsonString);
65-
return Validate(jsonSchema, jsonDocument);
66-
}
67-
catch (JsonException exc)
66+
var jsonDocument = JsonDocument.Parse(jsonString);
67+
return Validate(_spdxSchema, jsonDocument);
68+
}
69+
catch (JsonException exc)
70+
{
71+
return new ValidationResult
6872
{
69-
return new ValidationResult
70-
{
71-
Valid = false,
72-
Messages = new List<string> { exc.Message }
73-
};
74-
}
73+
Valid = false,
74+
Messages = new List<string> { exc.Message }
75+
};
7576
}
7677
}
7778

@@ -93,7 +94,7 @@ private static ValidationResult Validate(JsonSchema schema, JsonDocument jsonDoc
9394
// there will be no nested results
9495
foreach (var detail in result.Details)
9596
{
96-
if (detail.HasErrors)
97+
if (detail.Errors != null)
9798
{
9899
foreach (var error in detail.Errors)
99100
{

src/CycloneDX.Utils/CycloneDX.Utils.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
15+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103">
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
<PrivateAssets>all</PrivateAssets>
1818
</PackageReference>

0 commit comments

Comments
 (0)