Skip to content

Commit 28b94a7

Browse files
committed
feat: prepare "contrib" area
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
1 parent b88d831 commit 28b94a7

File tree

6 files changed

+167
-108
lines changed

6 files changed

+167
-108
lines changed

cyclonedx/contrib/component/builders.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
class ComponentBuilder:
2929

30-
def make_for_file(self, absolute_file_path: str, name: Optional[str]) -> Component:
30+
def make_for_file(self, absolute_file_path: str, *,
31+
name: Optional[str]) -> Component:
3132
"""
3233
Helper method to create a :class:`cyclonedx.model.component.Component`
3334
that represents the provided local file as a Component.

cyclonedx/contrib/hash/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This file is part of CycloneDX Python Library
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
# Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
"""Hash related functionality"""
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# This file is part of CycloneDX Python Library
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
# Copyright (c) OWASP Foundation. All Rights Reserved.
17+
18+
"""Hash related factories"""
19+
20+
__all__ = ['HashTypeFactory']
21+
22+
from ...model import HashType, HashAlgorithm
23+
24+
from ...exception.model import UnknownHashTypeException
25+
26+
27+
28+
_MAP_HASHLIB: dict[str, HashAlgorithm] = {
29+
# from hashlib.algorithms_guaranteed
30+
'md5': HashAlgorithm.MD5,
31+
'sha1': HashAlgorithm.SHA_1,
32+
# sha224:
33+
'sha256': HashAlgorithm.SHA_256,
34+
'sha384': HashAlgorithm.SHA_384,
35+
'sha512': HashAlgorithm.SHA_512,
36+
# blake2b:
37+
# blake2s:
38+
# sha3_224:
39+
'sha3_256': HashAlgorithm.SHA3_256,
40+
'sha3_384': HashAlgorithm.SHA3_384,
41+
'sha3_512': HashAlgorithm.SHA3_512,
42+
# shake_128:
43+
# shake_256:
44+
}
45+
46+
class HashTypeFactory:
47+
48+
def from_hashlib_alg(self, hashlib_alg: str, content: str) -> HashType:
49+
"""
50+
Attempts to convert a hashlib-algorithm to our internal model classes.
51+
52+
Args:
53+
hashlib_alg:
54+
Hash algorith - like it is used by `hashlib`.
55+
Example: `sha256`.
56+
57+
content:
58+
Hash value.
59+
60+
Raises:
61+
`UnknownHashTypeException` if the algorithm of hash cannot be determined.
62+
63+
Returns:
64+
An instance of `HashType`.
65+
"""
66+
alg = _MAP_HASHLIB.get(hashlib_alg.lower())
67+
if alg is None:
68+
raise UnknownHashTypeException(f'Unable to determine hash alg for {hashlib_alg!r}')
69+
return HashType(alg=alg, content=content)
70+
71+
def from_composite_str(self, composite_hash: str) -> HashType:
72+
"""
73+
Attempts to convert a string which includes both the Hash Algorithm and Hash Value and represent using our
74+
internal model classes.
75+
76+
Args:
77+
composite_hash:
78+
Composite Hash string of the format `HASH_ALGORITHM`:`HASH_VALUE`.
79+
Example: `sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b`.
80+
81+
Valid case insensitive prefixes are:
82+
`md5`, `sha1`, `sha256`, `sha384`, `sha512`, `blake2b256`, `blake2b384`, `blake2b512`,
83+
`blake2256`, `blake2384`, `blake2512`, `sha3-256`, `sha3-384`, `sha3-512`,
84+
`blake3`.
85+
86+
Raises:
87+
`UnknownHashTypeException` if the type of hash cannot be determined.
88+
89+
Returns:
90+
An instance of `HashType`.
91+
"""
92+
parts = composite_hash.split(':')
93+
94+
algorithm_prefix = parts[0].lower()
95+
if algorithm_prefix == 'md5':
96+
return HashType(
97+
alg=HashAlgorithm.MD5,
98+
content=parts[1].lower()
99+
)
100+
elif algorithm_prefix[0:4] == 'sha3':
101+
return HashType(
102+
alg=getattr(HashAlgorithm, f'SHA3_{algorithm_prefix[5:]}'),
103+
content=parts[1].lower()
104+
)
105+
elif algorithm_prefix == 'sha1':
106+
return HashType(
107+
alg=HashAlgorithm.SHA_1,
108+
content=parts[1].lower()
109+
)
110+
elif algorithm_prefix[0:3] == 'sha':
111+
# This is actually SHA2...
112+
return HashType(
113+
alg=getattr(HashAlgorithm, f'SHA_{algorithm_prefix[3:]}'),
114+
content=parts[1].lower()
115+
)
116+
elif algorithm_prefix[0:7] == 'blake2b':
117+
return HashType(
118+
alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[7:]}'),
119+
content=parts[1].lower()
120+
)
121+
elif algorithm_prefix[0:6] == 'blake2':
122+
return HashType(
123+
alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[6:]}'),
124+
content=parts[1].lower()
125+
)
126+
elif algorithm_prefix[0:6] == 'blake3':
127+
return HashType(
128+
alg=HashAlgorithm.BLAKE3,
129+
content=parts[1].lower()
130+
)
131+
raise UnknownHashTypeException(f'Unable to determine hash type from {composite_hash!r}')

cyclonedx/exception/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class UnknownHashTypeException(CycloneDxModelException):
116116
"""
117117
Exception raised when we are unable to determine the type of hash from a composite hash string.
118118
"""
119-
pass
119+
pass # TODO research deprecation of this...
120120

121121

122122
class LicenseExpressionAlongWithOthersException(CycloneDxModelException):

cyclonedx/model/__init__.py

Lines changed: 14 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@
3737
import py_serializable as serializable
3838
from sortedcontainers import SortedSet
3939

40+
from contrib.hash.factories import HashTypeFactory
4041
from .._internal.compare import ComparableTuple as _ComparableTuple
41-
from ..exception.model import InvalidLocaleTypeException, InvalidUriException, UnknownHashTypeException
42+
from ..exception.model import InvalidLocaleTypeException, InvalidUriException
4243
from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException
4344
from ..schema.schema import (
4445
SchemaVersion1Dot0,
@@ -366,25 +367,6 @@ def xml_denormalize(cls, o: 'XmlElement', *,
366367
]
367368

368369

369-
_MAP_HASHLIB: dict[str, HashAlgorithm] = {
370-
# from hashlib.algorithms_guaranteed
371-
'md5': HashAlgorithm.MD5,
372-
'sha1': HashAlgorithm.SHA_1,
373-
# sha224:
374-
'sha256': HashAlgorithm.SHA_256,
375-
'sha384': HashAlgorithm.SHA_384,
376-
'sha512': HashAlgorithm.SHA_512,
377-
# blake2b:
378-
# blake2s:
379-
# sha3_224:
380-
'sha3_256': HashAlgorithm.SHA3_256,
381-
'sha3_384': HashAlgorithm.SHA3_384,
382-
'sha3_512': HashAlgorithm.SHA3_512,
383-
# shake_128:
384-
# shake_256:
385-
}
386-
387-
388370
@serializable.serializable_class
389371
class HashType:
390372
"""
@@ -395,91 +377,27 @@ class HashType:
395377
"""
396378

397379
@staticmethod
398-
def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType': # TODO: move to contrib
399-
"""
400-
Attempts to convert a hashlib-algorithm to our internal model classes.
401-
402-
Args:
403-
hashlib_alg:
404-
Hash algorith - like it is used by `hashlib`.
405-
Example: `sha256`.
380+
def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType':
381+
"""Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alge`.
406382
407-
content:
408-
Hash value.
409-
410-
Raises:
411-
`UnknownHashTypeException` if the algorithm of hash cannot be determined.
383+
Attempts to convert a hashlib-algorithm to our internal model classes.
412384
413-
Returns:
414-
An instance of `HashType`.
385+
.. deprecated:: next
386+
Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_hashlib_alg()`` instead.
415387
"""
416-
alg = _MAP_HASHLIB.get(hashlib_alg.lower())
417-
if alg is None:
418-
raise UnknownHashTypeException(f'Unable to determine hash alg for {hashlib_alg!r}')
419-
return HashType(alg=alg, content=content)
388+
return HashTypeFactory().from_hashlib_alg(hashlib_alg, content)
420389

421390
@staticmethod
422-
def from_composite_str(composite_hash: str) -> 'HashType': # TODO: move to contrib
423-
"""
391+
def from_composite_str(composite_hash: str) -> 'HashType':
392+
"""Deprecated — Alias of :func:`cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str`.
393+
424394
Attempts to convert a string which includes both the Hash Algorithm and Hash Value and represent using our
425395
internal model classes.
426396
427-
Args:
428-
composite_hash:
429-
Composite Hash string of the format `HASH_ALGORITHM`:`HASH_VALUE`.
430-
Example: `sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b`.
431-
432-
Valid case insensitive prefixes are:
433-
`md5`, `sha1`, `sha256`, `sha384`, `sha512`, `blake2b256`, `blake2b384`, `blake2b512`,
434-
`blake2256`, `blake2384`, `blake2512`, `sha3-256`, `sha3-384`, `sha3-512`,
435-
`blake3`.
436-
437-
Raises:
438-
`UnknownHashTypeException` if the type of hash cannot be determined.
439-
440-
Returns:
441-
An instance of `HashType`.
397+
.. deprecated:: next
398+
Use ``cyclonedx.contrib.hash.factories.HashTypeFactory.from_composite_str()`` instead.
442399
"""
443-
parts = composite_hash.split(':')
444-
445-
algorithm_prefix = parts[0].lower()
446-
if algorithm_prefix == 'md5':
447-
return HashType(
448-
alg=HashAlgorithm.MD5,
449-
content=parts[1].lower()
450-
)
451-
elif algorithm_prefix[0:4] == 'sha3':
452-
return HashType(
453-
alg=getattr(HashAlgorithm, f'SHA3_{algorithm_prefix[5:]}'),
454-
content=parts[1].lower()
455-
)
456-
elif algorithm_prefix == 'sha1':
457-
return HashType(
458-
alg=HashAlgorithm.SHA_1,
459-
content=parts[1].lower()
460-
)
461-
elif algorithm_prefix[0:3] == 'sha':
462-
# This is actually SHA2...
463-
return HashType(
464-
alg=getattr(HashAlgorithm, f'SHA_{algorithm_prefix[3:]}'),
465-
content=parts[1].lower()
466-
)
467-
elif algorithm_prefix[0:7] == 'blake2b':
468-
return HashType(
469-
alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[7:]}'),
470-
content=parts[1].lower()
471-
)
472-
elif algorithm_prefix[0:6] == 'blake2':
473-
return HashType(
474-
alg=getattr(HashAlgorithm, f'BLAKE2B_{algorithm_prefix[6:]}'),
475-
content=parts[1].lower()
476-
)
477-
elif algorithm_prefix[0:6] == 'blake3':
478-
return HashType(
479-
alg=HashAlgorithm.BLAKE3,
480-
content=parts[1].lower()
481-
)
482-
raise UnknownHashTypeException(f'Unable to determine hash type from {composite_hash!r}')
400+
return HashTypeFactory().from_composite_str(composite_hash)
483401

484402
def __init__(
485403
self, *,

cyclonedx/model/component.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -960,19 +960,10 @@ def for_file(absolute_file_path: str, path_for_bom: Optional[str]) -> 'Component
960960
961961
Helper method to create a Component that represents the provided local file as a Component.
962962
963-
Args:
964-
absolute_file_path:
965-
Absolute path to the file you wish to represent
966-
path_for_bom:
967-
Optionally, if supplied this is the path that will be used to identify the file in the BOM
968-
969-
Returns:
970-
`Component` representing the supplied file
971-
972963
.. deprecated:: next
973964
Use ``cyclonedx.contrib.component.builders.ComponentBuilder.make_for_file()`` instead.
974965
"""
975-
component = ComponentBuilder().make_for_file(absolute_file_path, path_for_bom)
966+
component = ComponentBuilder().make_for_file(absolute_file_path, name=path_for_bom)
976967
sha1_hash = next(h.content for h in component.hashes if h.alg is HashAlgorithm.SHA_1)
977968
component.version=f'0.0.0-{sha1_hash[0:12]}'
978969
component.purl=PackageURL( # DEPRECATED: a file has no PURL!

0 commit comments

Comments
 (0)