From 802ab3d9d8a043572044c7f824e490e72639b3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Boutemy?= Date: Thu, 22 Dec 2022 17:06:41 +0100 Subject: [PATCH] add Markdown serializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hervé Boutemy --- src/cyclonedx/CliUtils.cs | 11 +- src/cyclonedx/Commands/ConvertFormat.cs | 1 + .../Serialization/MarkdownSerializer.cs | 197 ++++++++++++++++++ .../commons-compress-1.12.md | 33 +++ 4 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 src/cyclonedx/Serialization/MarkdownSerializer.cs create mode 100644 tests/cyclonedx.tests/Resources/MarkdownSerializer/commons-compress-1.12.md diff --git a/src/cyclonedx/CliUtils.cs b/src/cyclonedx/CliUtils.cs index 805d982..ba46717 100644 --- a/src/cyclonedx/CliUtils.cs +++ b/src/cyclonedx/CliUtils.cs @@ -57,6 +57,10 @@ public static ConvertFormat AutoDetectConvertBomFormat(string filename) { return ConvertFormat.csv; } + if (fileExtension == ".md") + { + return ConvertFormat.markdown; + } else if (filename.ToLowerInvariant().EndsWith(".spdx.json", StringComparison.InvariantCulture)) { return ConvertFormat.spdxjson; @@ -116,7 +120,7 @@ public static async Task InputBomHelper(string filename, ConvertFormat form } } - + if (format == ConvertFormat.csv) { using var inputStream = filename == null ? Console.OpenStandardInput() : File.OpenRead(filename); @@ -211,6 +215,11 @@ public static async Task OutputBomHelper(Bom bom, ConvertFormat format, Spe var bomBytes = Encoding.UTF8.GetBytes(bomString); stream.Write(bomBytes); break; + case ConvertFormat.markdown: + var mdString = MarkdownSerializer.Serialize(bom); + var mdBytes = Encoding.UTF8.GetBytes(mdString); + stream.Write(mdBytes); + break; case ConvertFormat.spdxjson: var spdxDoc = bom.ToSpdx(); await CycloneDX.Spdx.Serialization.JsonSerializer.SerializeAsync(spdxDoc, stream); diff --git a/src/cyclonedx/Commands/ConvertFormat.cs b/src/cyclonedx/Commands/ConvertFormat.cs index 5c19a54..4116dc3 100644 --- a/src/cyclonedx/Commands/ConvertFormat.cs +++ b/src/cyclonedx/Commands/ConvertFormat.cs @@ -25,5 +25,6 @@ public enum ConvertFormat protobuf, csv, spdxjson, + markdown, } } diff --git a/src/cyclonedx/Serialization/MarkdownSerializer.cs b/src/cyclonedx/Serialization/MarkdownSerializer.cs new file mode 100644 index 0000000..428e7f2 --- /dev/null +++ b/src/cyclonedx/Serialization/MarkdownSerializer.cs @@ -0,0 +1,197 @@ +// This file is part of CycloneDX CLI Tool +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) OWASP Foundation. All Rights Reserved. +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Text; +using CycloneDX.Models; + +namespace CycloneDX.Cli.Serialization +{ + public static class MarkdownSerializer + { + public static string Serialize(Bom bom) + { + Contract.Requires(bom != null); + using (var stream = new MemoryStream()) + using (var writer = new StreamWriter(stream)) + { + if (bom.Metadata?.Component != null) + { + writer.WriteLine("# " + bom.Metadata.Component.Name + " (" + bom.Metadata.Component.Type + ")"); + } + writer.WriteLine("BOM " + bom.SerialNumber + " " + bom.BomFormat + " " + bom.SpecVersionString + " " + bom.Version + " " + bom.Metadata?.Timestamp); + writer.WriteLine(); + + if (bom.Metadata != null) + { + var m = bom.Metadata; + + // BOM tools or authors + if (m.Tools != null) + { + writer.Write("BOM done with tools: "); + foreach (var t in m.Tools) + { + writer.Write(t.Name + " " + t.Version + " (" + t.Vendor + "), "); + } + writer.WriteLine(); + } + else if (m.Authors != null) + { + writer.Write("BOM authored by: "); + foreach (var a in m.Authors) + { + writer.Write(a.Name + ", "); + } + writer.WriteLine(); + } + + // Component + if (m.Component != null) + { + var c = m.Component; + + writer.Write(">"); + if (c.Group != null) + { + writer.Write(" _group:_ **" + c.Group + "**"); + } + if (c.Name != null) + { + writer.Write(" _name:_ **" + c.Name + "**"); + } + if (c.Version != null) + { + writer.Write(" _version:_ **" + c.Version + "**"); + } + if (c.Name == null && c.Purl != null) + { + writer.Write(" _purl:_ **" + c.Purl + "**"); + } + if (c.Cpe != null) + { + writer.Write(" _CPE:_ **" + c.Cpe + "**"); + } + writer.WriteLine(); + if (c.Description != null) + { + writer.WriteLine(">"); + writer.WriteLine("> " + c.Description); + } + writer.WriteLine(); + + if (c.ExternalReferences != null) + { + writer.Write("Component external references: "); + foreach (var er in c.ExternalReferences) + { + writer.Write("[" + er.Type + "](" + er.Url + "), "); + } + writer.WriteLine(); + } + } + + if (bom.ExternalReferences != null) + { + writer.WriteLine("## ExternalReferences"); + writer.WriteLine("not supported yet"); // how is it different from bom.Metadata.Component.ExternalReferences? + writer.WriteLine(); + } + + if (bom.Components != null) + { + writer.WriteLine("## Components"); + foreach (var c in bom.Components) + { + writer.Write("1. " + c.Type); + if (c.Group != null) + { + writer.Write(" _group:_ **" + c.Group + "**"); + } + if (c.Name != null) + { + writer.Write(" _name:_ **" + c.Name + "**"); + } + if (c.Version != null) + { + writer.Write(" _version:_ **" + c.Version + "**"); + } + if (c.Scope != null) + { + writer.Write(" _scope:_ " + c.Scope); + } + if (c.Purl != null) + { + writer.WriteLine(" \\"); + writer.Write(" _purl:_ " + c.Purl); + } + writer.WriteLine(); + } + writer.WriteLine(); + } + + if (bom.Services != null) + { + writer.WriteLine("## Services"); + writer.WriteLine("not supported yet"); + writer.WriteLine(); + } + + if (bom.Dependencies != null) { + writer.WriteLine("## Dependencies"); + WriteDependencies(writer, bom.Dependencies, ""); + writer.WriteLine(); + } + + if (bom.Compositions != null) + { + writer.WriteLine("## Compositions"); + writer.WriteLine("not supported yet"); + writer.WriteLine(); + } + + if (bom.Vulnerabilities != null) + { + writer.WriteLine("## Vulnerabilities"); + writer.WriteLine("not supported yet"); + writer.WriteLine(); + } + } + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); + } + } + + private static void WriteDependencies(StreamWriter writer, List dependencies, string indent) + { + foreach (var d in dependencies) + { + writer.WriteLine(indent + "- " + d.Ref); + if (d.Dependencies != null) { + WriteDependencies(writer, d.Dependencies, indent + " "); + } + } + } + + public static Bom Deserialize(string csv) + { + return null; + } + } +} diff --git a/tests/cyclonedx.tests/Resources/MarkdownSerializer/commons-compress-1.12.md b/tests/cyclonedx.tests/Resources/MarkdownSerializer/commons-compress-1.12.md new file mode 100644 index 0000000..3e50ffd --- /dev/null +++ b/tests/cyclonedx.tests/Resources/MarkdownSerializer/commons-compress-1.12.md @@ -0,0 +1,33 @@ +# commons-compress (Library) +BOM urn:uuid:381cbf4c-3009-4a42-a4d0-7188e72fbfac CycloneDX 1.4 1 25/10/2022 19:46:32 + +BOM done with tools: CycloneDX Maven plugin 2.7.1 (OWASP Foundation), +> _group:_ **org.apache.commons** _name:_ **commons-compress** _version:_ **1.22** +> +> Apache Commons Compress software defines an API for working with compression and archive formats. These include: bzip2, gzip, pack200, lzma, xz, Snappy, traditional Unix Compress, DEFLATE, DEFLATE64, LZ4, Brotli, Zstandard and ar, cpio, jar, tar, zip, dump, 7z, arj. + +Component external references: [Website](https://www.apache.org/), [BuildSystem](https://github.com/apache/commons-parent/actions), [Distribution](https://repository.apache.org/service/local/staging/deploy/maven2), [IssueTracker](https://issues.apache.org/jira/browse/COMPRESS), [MailingList](https://mail-archives.apache.org/mod_mbox/commons-user/), [Vcs](https://gitbox.apache.org/repos/asf?p=commons-compress.git), +## Components +1. Library _group:_ **com.github.luben** _name:_ **zstd-jni** _version:_ **1.5.2-5** _scope:_ Required \ + _purl:_ pkg:maven/com.github.luben/zstd-jni@1.5.2-5?type=jar +1. Library _group:_ **org.brotli** _name:_ **dec** _version:_ **0.1.2** _scope:_ Required \ + _purl:_ pkg:maven/org.brotli/dec@0.1.2?type=jar +1. Library _group:_ **org.tukaani** _name:_ **xz** _version:_ **1.9** _scope:_ Required \ + _purl:_ pkg:maven/org.tukaani/xz@1.9?type=jar +1. Library _group:_ **org.ow2.asm** _name:_ **asm** _version:_ **9.4** _scope:_ Required \ + _purl:_ pkg:maven/org.ow2.asm/asm@9.4?type=jar +1. Library _group:_ **org.osgi** _name:_ **org.osgi.core** _version:_ **6.0.0** _scope:_ Optional \ + _purl:_ pkg:maven/org.osgi/org.osgi.core@6.0.0?type=jar + +## Dependencies +- pkg:maven/org.apache.commons/commons-compress@1.22?type=jar + - pkg:maven/com.github.luben/zstd-jni@1.5.2-5?type=jar + - pkg:maven/org.brotli/dec@0.1.2?type=jar + - pkg:maven/org.tukaani/xz@1.9?type=jar + - pkg:maven/org.ow2.asm/asm@9.4?type=jar + - pkg:maven/org.osgi/org.osgi.core@6.0.0?type=jar +- pkg:maven/com.github.luben/zstd-jni@1.5.2-5?type=jar +- pkg:maven/org.brotli/dec@0.1.2?type=jar +- pkg:maven/org.tukaani/xz@1.9?type=jar +- pkg:maven/org.ow2.asm/asm@9.4?type=jar +- pkg:maven/org.osgi/org.osgi.core@6.0.0?type=jar