Skip to content

Commit 5ca72e3

Browse files
committed
feat(msbuild): improve SBOM generation and XML handling
- Add inline tasks for URL- and XML-encoding in `CycloneDX.MSBuild.targets`. - Replace CDATA blocks with XML-escaped values for better compatibility. - Extend XML metadata to include escaped `Description`, `Authors`, `Copyright`, and `License` fields. - Remove old URL-encoding logic and use the new `UrlEncodeCycloneDxProps` task. - Add `cyclonedx` tool (v5.5.0) to `dotnet-tools.json` for SBOM generation. - Improve maintainability and ensure robust XML data formatting.
1 parent 0b75b3e commit 5ca72e3

File tree

1 file changed

+105
-45
lines changed

1 file changed

+105
-45
lines changed

src/CycloneDX.MSBuild/build/CycloneDX.MSBuild.targets

Lines changed: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,53 @@
2525
3. GenerateCycloneDxSbom (after build, before pack)
2626
-->
2727

28+
<!-- Inline task to URL-encode component name and version -->
29+
<UsingTask TaskName="UrlEncodeCycloneDxProps" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core">
30+
<ParameterGroup>
31+
<ComponentName ParameterType="System.String" Required="true" />
32+
<Version ParameterType="System.String" Required="true" />
33+
<EncodedComponentName ParameterType="System.String" Output="true" />
34+
<EncodedVersion ParameterType="System.String" Output="true" />
35+
</ParameterGroup>
36+
<Task>
37+
<Reference Include="System" />
38+
<Code Type="Fragment" Language="cs">
39+
<![CDATA[
40+
EncodedComponentName = System.Uri.EscapeDataString(ComponentName ?? "");
41+
EncodedVersion = System.Uri.EscapeDataString(Version ?? "");
42+
]]>
43+
</Code>
44+
</Task>
45+
</UsingTask>
46+
47+
<!-- Inline task to XML-escape strings -->
48+
<UsingTask TaskName="XmlEscapeCycloneDxProps" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core">
49+
<ParameterGroup>
50+
<InputString ParameterType="System.String" Required="true" />
51+
<EscapedString ParameterType="System.String" Output="true" />
52+
</ParameterGroup>
53+
<Task>
54+
<Reference Include="System.Xml" />
55+
<Code Type="Fragment" Language="cs">
56+
<![CDATA[
57+
if (!string.IsNullOrEmpty(InputString))
58+
{
59+
EscapedString = InputString
60+
.Replace("&", "&amp;")
61+
.Replace("<", "&lt;")
62+
.Replace(">", "&gt;")
63+
.Replace("\"", "&quot;")
64+
.Replace("'", "&apos;");
65+
}
66+
else
67+
{
68+
EscapedString = "";
69+
}
70+
]]>
71+
</Code>
72+
</Task>
73+
</UsingTask>
74+
2875
<Target Name="ValidateCycloneDxConfiguration"
2976
BeforeTargets="Build"
3077
Condition="'$(GenerateCycloneDxSbom)' == 'true'">
@@ -136,23 +183,6 @@
136183
<!-- Build package URL (purl) -->
137184
</PropertyGroup>
138185

139-
<!-- Inline task to URL-encode component name and version -->
140-
<UsingTask TaskName="UrlEncodeCycloneDxProps" TaskFactory="CodeTaskFactory" AssemblyName="Microsoft.Build.Tasks.Core">
141-
<ParameterGroup>
142-
ComponentName ParameterType="System.String" Required="true" />
143-
Version ParameterType="System.String" Required="true" />
144-
EncodedComponentName ParameterType="System.String" Output="true" />
145-
EncodedVersion ParameterType="System.String" Output="true" />
146-
</ParameterGroup>
147-
<Task>
148-
<Reference Include="System" />
149-
<Code Type="Fragment" Language="cs">
150-
EncodedComponentName = System.Uri.EscapeDataString(ComponentName ?? "");
151-
EncodedVersion = System.Uri.EscapeDataString(Version ?? "");
152-
</Code>
153-
</Task>
154-
</UsingTask>
155-
156186
<UrlEncodeCycloneDxProps
157187
ComponentName="$(_CycloneDxComponentName)"
158188
Version="$(_CycloneDxVersion)">
@@ -163,48 +193,78 @@
163193
<PropertyGroup>
164194
<_CycloneDxPurl>pkg:nuget/$(_CycloneDxComponentNameEncoded)@$(_CycloneDxVersionEncoded)</_CycloneDxPurl>
165195
</PropertyGroup>
196+
197+
<!-- Escape all values for XML -->
198+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxPurl)">
199+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxPurlEscaped" />
200+
</XmlEscapeCycloneDxProps>
201+
202+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxComponentName)">
203+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxComponentNameEscaped" />
204+
</XmlEscapeCycloneDxProps>
205+
206+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxVersion)">
207+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxVersionEscaped" />
208+
</XmlEscapeCycloneDxProps>
209+
210+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxDescription)" Condition="'$(_CycloneDxDescription)' != ''">
211+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxDescriptionEscaped" />
212+
</XmlEscapeCycloneDxProps>
213+
214+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxAuthors)" Condition="'$(_CycloneDxAuthors)' != ''">
215+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxAuthorsEscaped" />
216+
</XmlEscapeCycloneDxProps>
217+
218+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxCopyright)" Condition="'$(_CycloneDxCopyright)' != ''">
219+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxCopyrightEscaped" />
220+
</XmlEscapeCycloneDxProps>
221+
222+
<XmlEscapeCycloneDxProps InputString="$(_CycloneDxLicense)" Condition="'$(_CycloneDxLicense)' != ''">
223+
<Output TaskParameter="EscapedString" PropertyName="_CycloneDxLicenseEscaped" />
224+
</XmlEscapeCycloneDxProps>
225+
166226
<Message Importance="low" Text="[CycloneDX] Generating metadata from assembly information..." />
167227
<Message Importance="low" Text="[CycloneDX] Version: $(_CycloneDxVersion)" />
168228
<Message Importance="low" Text="[CycloneDX] Name: $(_CycloneDxComponentName)" />
169229
<Message Importance="low" Text="[CycloneDX] Authors: $(_CycloneDxAuthors)" Condition="'$(_CycloneDxAuthors)' != ''" />
170230

171-
<!-- Build metadata XML content -->
231+
<!-- Build metadata XML content using escaped values without CDATA interleaving -->
172232
<PropertyGroup>
173-
<_CycloneDxMetadataContent><![CDATA[<?xml version="1.0" encoding="utf-8"?>
174-
<bom xmlns="http://cyclonedx.org/schema/bom/1.6">
175-
<metadata>
176-
<component type="application" bom-ref="$([System.Security.SecurityElement::Escape('$(_CycloneDxPurl)'))]">
177-
<name>$([System.Security.SecurityElement::Escape('$(_CycloneDxComponentName)'))]</name>
178-
<version>$([System.Security.SecurityElement::Escape('$(_CycloneDxVersion)'))]</version>]]></_CycloneDxMetadataContent>
233+
<_CycloneDxMetadataContent>
234+
&lt;?xml version="1.0" encoding="utf-8"?&gt;
235+
&lt;bom xmlns="http://cyclonedx.org/schema/bom/1.6"&gt;
236+
&lt;metadata&gt;
237+
&lt;component type="application" bom-ref="$(_CycloneDxPurlEscaped)"&gt;
238+
&lt;name&gt;$(_CycloneDxComponentNameEscaped)&lt;/name&gt;
239+
&lt;version&gt;$(_CycloneDxVersionEscaped)&lt;/version&gt;
240+
</_CycloneDxMetadataContent>
179241

180242
<!-- Add description if available -->
181-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent)<![CDATA[
182-
<description>$([System.Security.SecurityElement::Escape('$(_CycloneDxDescription)'))]</description>]]></_CycloneDxMetadataContent>
243+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxDescription)' != ''">$(_CycloneDxMetadataContent) &lt;description&gt;$(_CycloneDxDescriptionEscaped)&lt;/description&gt;</_CycloneDxMetadataContent>
183244

184245
<!-- Add authors/supplier if available -->
185-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent)<![CDATA[
186-
<supplier>
187-
<name>$([System.Security.SecurityElement::Escape('$(_CycloneDxAuthors)'))]</name>
188-
</supplier>]]></_CycloneDxMetadataContent>
246+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxAuthors)' != ''">$(_CycloneDxMetadataContent)
247+
&lt;supplier&gt;
248+
&lt;name&gt;$(_CycloneDxAuthorsEscaped)&lt;/name&gt;
249+
&lt;/supplier&gt;</_CycloneDxMetadataContent>
189250

190251
<!-- Add copyright if available -->
191-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent)<![CDATA[
192-
<copyright>$([System.Security.SecurityElement::Escape('$(_CycloneDxCopyright)'))]</copyright>]]></_CycloneDxMetadataContent>
252+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxCopyright)' != ''">$(_CycloneDxMetadataContent) &lt;copyright&gt;$(_CycloneDxCopyrightEscaped)&lt;/copyright&gt;</_CycloneDxMetadataContent>
193253

194254
<!-- Add license if available -->
195-
<_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent)<![CDATA[
196-
<licenses>
197-
<license>
198-
<id>$([System.Security.SecurityElement::Escape('$(_CycloneDxLicense)'))]</id>
199-
</license>
200-
</licenses>]]></_CycloneDxMetadataContent>
201-
202-
<!-- Add purl -->
203-
<_CycloneDxMetadataContent>$(_CycloneDxMetadataContent)<![CDATA[
204-
<purl>$([System.Security.SecurityElement::Escape('$(_CycloneDxPurl)'))]</purl>
205-
</component>
206-
</metadata>
207-
</bom>]]></_CycloneDxMetadataContent>
255+
<_CycloneDxMetadataContent Condition="'$(_CycloneDxLicense)' != ''">$(_CycloneDxMetadataContent)
256+
&lt;licenses&gt;
257+
&lt;license&gt;
258+
&lt;id&gt;$(_CycloneDxLicenseEscaped)&lt;/id&gt;
259+
&lt;/license&gt;
260+
&lt;/licenses&gt;</_CycloneDxMetadataContent>
261+
262+
<!-- Add purl and close tags -->
263+
<_CycloneDxMetadataContent>$(_CycloneDxMetadataContent)
264+
&lt;purl&gt;$(_CycloneDxPurlEscaped)&lt;/purl&gt;
265+
&lt;/component&gt;
266+
&lt;/metadata&gt;
267+
&lt;/bom&gt;</_CycloneDxMetadataContent>
208268
</PropertyGroup>
209269

210270
<!-- Write metadata to temporary file -->

0 commit comments

Comments
 (0)