2626
2727from typing import BinaryIO , Dict , Optional
2828from copy import deepcopy
29+ from io import BytesIO
2930from logging import getLogger
3031
3132from requests .models import Response
33+ from xmltodict import parse as xmltodict_parse
3234
3335# pylint:disable=unused-import # To prevent cyclical import errors forward referencing is used
3436# pylint:disable=cyclic-import # but pylint doesn't understand this feature
4547from . import publisher , uploader , withdrawer
4648from .dictmerge import _deepmerge
4749from .sbommetadata import SBOM
50+ from .utils import get_url
4851
4952LOGGER = getLogger (__name__ )
5053
@@ -66,12 +69,147 @@ def __init__(self, archivist: "type_helper.Archivist"):
6669 def __str__ (self ) -> str :
6770 return f"SBOMSClient({ self ._archivist .url } )"
6871
72+ @staticmethod
73+ def parse (data : Dict ) -> Dict : # pragma: no cover
74+ """
75+ parse the sbom and extract pertinent informtion
76+
77+ Args:
78+ data (dict): dictionary
79+
80+ A YAML representation of the data argument would be:
81+
82+ .. code-block:: yaml
83+
84+ filename: functests/test_resources/sboms/gen1.xml
85+
86+ OR
87+
88+ .. code-block:: yaml
89+
90+ url: https://some.hostname/cdx.xml
91+
92+ Either 'filename' or 'url' is required.
93+
94+ Returns:
95+
96+ A dict suitable for adding to an asset or event creation
97+
98+ """
99+ result = None
100+ filename = data .get ("filename" )
101+ if filename is not None :
102+ with open (filename , "rb" ) as fd :
103+ sbom = xmltodict_parse (fd , xml_attribs = True , disable_entities = False )
104+
105+ else :
106+ url = data ["url" ]
107+ fd = BytesIO ()
108+ get_url (url , fd )
109+ sbom = xmltodict_parse (fd , xml_attribs = True , disable_entities = False )
110+
111+ b = sbom ["bom" ]
112+ m = b ["metadata" ]
113+ c = m ["component" ]
114+ hash_value = c ["hashes" ]["hash" ]["#text" ]
115+ result = {
116+ "author" : c ["author" ],
117+ "component" : c ["name" ],
118+ "hash" : hash_value ,
119+ "supplier" : c ["supplier" ]["name" ],
120+ "uuid" : b ["@serialNumber" ],
121+ "version" : c ["version" ],
122+ }
123+
124+ return result
125+
126+ def create (self , data : Dict ) -> Dict : # pragma: no cover
127+ """
128+ Create an sbom and return struct suitable for use in an asset
129+ or event creation.
130+
131+ Args:
132+ data (dict): dictionary
133+
134+ A YAML representation of the data argument would be:
135+
136+ .. code-block:: yaml
137+
138+ filename: functests/test_resources/sboms/gen1.xml
139+ content_type: text/xml
140+ confirm: True
141+ params:
142+ privacy: PRIVATE
143+
144+ OR
145+
146+ .. code-block:: yaml
147+
148+ url: https://some.hostname/cdx.xml
149+ content_type: text/xml
150+ confirm: True
151+ params:
152+ privacy: PRIVATE
153+
154+ Either 'filename' or 'url' is required.
155+ 'content_type' is required.
156+
157+ Returns:
158+
159+ A dict suitable for adding to an asset or event creation
160+
161+ A YAML representation of the result would be:
162+
163+ .. code-block:: yaml
164+
165+ arc_display_name: Acme Generation1 SBOM
166+ arc_attachment_identity: sboms/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
167+ .....
168+
169+ """
170+ result = None
171+ filename = data .get ("filename" )
172+ if filename is not None :
173+ with open (filename , "rb" ) as fd :
174+ sbom = self .upload (
175+ fd ,
176+ confirm = data .get ("confirm" , False ),
177+ mtype = data .get ("content_type" ),
178+ params = data .get ("params" ),
179+ )
180+
181+ else :
182+ url = data ["url" ]
183+ fd = BytesIO ()
184+ get_url (url , fd )
185+ sbom = self .upload (
186+ fd ,
187+ confirm = data .get ("confirm" , False ),
188+ mtype = data .get ("content_type" ),
189+ params = data .get ("params" ),
190+ )
191+
192+ # response to sbom upload contains all the info we need.
193+ s = sbom .dict ()
194+ _ , hash_value = s ["hashes" ][0 ].split (":" )
195+ result = {
196+ "author" : "," .join (s ["authors" ]),
197+ "component" : s ["component" ],
198+ "identity" : s ["identity" ],
199+ "hash" : hash_value ,
200+ "supplier" : s ["supplier" ],
201+ "uuid" : s ["unique_id" ],
202+ "version" : s ["version" ],
203+ }
204+
205+ return result
206+
69207 def upload (
70208 self ,
71209 fd : BinaryIO ,
72210 * ,
73211 confirm : bool = False ,
74- mtype : str = "text/xml" ,
212+ mtype : Optional [ str ] = None ,
75213 params : Optional [Dict ] = None ,
76214 ) -> SBOM :
77215 """Create SBOM
@@ -89,6 +227,8 @@ def upload(
89227
90228 """
91229
230+ mtype = mtype or "text/xml"
231+
92232 LOGGER .debug ("Upload SBOM %s" , params )
93233
94234 sbom = SBOM (
0 commit comments