1818# Copyright (c) OWASP Foundation. All Rights Reserved.
1919
2020from enum import Enum
21+ from os .path import exists
2122from packageurl import PackageURL
2223from typing import List
2324
25+ from . import HashAlgorithm , HashType , sha1sum
2426from .vulnerability import Vulnerability
2527
26- PURL_TYPE_PREFIX = 'pypi'
27-
2828
2929class ComponentType (Enum ):
3030 """
@@ -51,6 +51,7 @@ class Component:
5151 See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_component
5252 """
5353 _type : ComponentType
54+ _package_url_type : str
5455 _name : str
5556 _version : str
5657 _qualifiers : str
@@ -59,15 +60,57 @@ class Component:
5960 _description : str = None
6061 _license : str = None
6162
63+ _hashes : List [HashType ] = []
6264 _vulnerabilites : List [Vulnerability ] = []
6365
64- def __init__ (self , name : str , version : str , qualifiers : str = None ,
65- component_type : ComponentType = ComponentType .LIBRARY ):
66+ @staticmethod
67+ def for_file (absolute_file_path : str , path_for_bom : str = None ):
68+ """
69+ Helper method to create a Component that represents the provided local file as a Component.
70+
71+ Args:
72+ absolute_file_path:
73+ Absolute path to the file you wish to represent
74+ path_for_bom:
75+ Optionally, if supplied this is the path that will be used to identify the file in the BOM
76+
77+ Returns:
78+ `Component` representing the supplied file
79+ """
80+ if not exists (absolute_file_path ):
81+ raise FileExistsError ('Supplied file path \' {}\' does not exist' .format (absolute_file_path ))
82+
83+ sha1_hash : str = sha1sum (filename = absolute_file_path )
84+
85+ return Component (
86+ name = path_for_bom if path_for_bom else absolute_file_path ,
87+ version = '0.0.0-{}' .format (sha1_hash [0 :12 ]),
88+ hashes = [
89+ HashType (algorithm = HashAlgorithm .SHA_1 , hash_value = sha1_hash )
90+ ],
91+ component_type = ComponentType .FILE ,
92+ package_url_type = 'generic'
93+ )
94+
95+ def __init__ (self , name : str , version : str , qualifiers : str = None , hashes : List [HashType ] = [],
96+ component_type : ComponentType = ComponentType .LIBRARY , package_url_type : str = 'pypi' ):
6697 self ._name = name
6798 self ._version = version
6899 self ._type = component_type
69100 self ._qualifiers = qualifiers
101+ self ._hashes = hashes
70102 self ._vulnerabilites = []
103+ self ._package_url_type = package_url_type
104+
105+ def add_hash (self , hash : HashType ):
106+ """
107+ Adds a hash that pins/identifies this Component.
108+
109+ Args:
110+ hash:
111+ `HashType` instance
112+ """
113+ self ._hashes .append (hash )
71114
72115 def add_vulnerability (self , vulnerability : Vulnerability ):
73116 """
@@ -100,6 +143,15 @@ def get_description(self) -> str:
100143 """
101144 return self ._description
102145
146+ def get_hashes (self ) -> List [HashType ]:
147+ """
148+ List of cryptographic hashes that identify this Component.
149+
150+ Returns:
151+ `List` of `HashType` objects where there are any hashes, else an empty `List`.
152+ """
153+ return self ._hashes
154+
103155 def get_license (self ) -> str :
104156 """
105157 Get the license of this Component.
@@ -125,7 +177,7 @@ def get_purl(self) -> str:
125177 Returns:
126178 PackageURL that reflects this Component as `str`.
127179 """
128- base_purl = 'pkg:{}/{}@{}' .format (PURL_TYPE_PREFIX , self ._name , self ._version )
180+ base_purl = 'pkg:{}/{}@{}' .format (self . _package_url_type , self ._name , self ._version )
129181 if self ._qualifiers :
130182 base_purl = '{}?{}' .format (base_purl , self ._qualifiers )
131183 return base_purl
@@ -213,7 +265,7 @@ def to_package_url(self) -> PackageURL:
213265 `packageurl.PackageURL` instance which represents this Component.
214266 """ ""
215267 return PackageURL (
216- type = PURL_TYPE_PREFIX ,
268+ type = self . _package_url_type ,
217269 name = self ._name ,
218270 version = self ._version ,
219271 qualifiers = self ._qualifiers
0 commit comments