Skip to content

Commit a82d88e

Browse files
authored
Merge branch 'main' into 6.0.0-dev
2 parents 1627280 + e490429 commit a82d88e

12 files changed

+409
-6
lines changed

cyclonedx/model/__init__.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import re
2424
from datetime import datetime, timezone
2525
from enum import Enum
26+
from functools import reduce
2627
from hashlib import sha1
2728
from itertools import zip_longest
2829
from json import loads as json_loads
@@ -631,16 +632,47 @@ class XsUri(serializable.helpers.BaseHelper):
631632
632633
.. note::
633634
See XSD definition for xsd:anyURI: http://www.datypic.com/sc/xsd/t-xsd_anyURI.html
635+
See JSON Schema definition for iri-reference: https://tools.ietf.org/html/rfc3987
634636
"""
635637

636638
_INVALID_URI_REGEX = re.compile(r'%(?![0-9A-F]{2})|#.*#', re.IGNORECASE + re.MULTILINE)
637639

640+
__SPEC_REPLACEMENTS = (
641+
(' ', '%20'),
642+
('[', '%5B'),
643+
(']', '%5D'),
644+
('<', '%3C'),
645+
('>', '%3E'),
646+
('{', '%7B'),
647+
('}', '%7D'),
648+
)
649+
650+
@staticmethod
651+
def __spec_replace(v: str, r: Tuple[str, str]) -> str:
652+
return v.replace(*r)
653+
654+
@classmethod
655+
def _spec_migrate(cls, o: str) -> str:
656+
"""
657+
Make a string valid to
658+
- XML::anyURI spec.
659+
- JSON::iri-reference spec.
660+
661+
BEST EFFORT IMPLEMENTATION
662+
663+
@see http://www.w3.org/TR/xmlschema-2/#anyURI
664+
@see http://www.datypic.com/sc/xsd/t-xsd_anyURI.html
665+
@see https://datatracker.ietf.org/doc/html/rfc2396
666+
@see https://datatracker.ietf.org/doc/html/rfc3987
667+
"""
668+
return reduce(cls.__spec_replace, cls.__SPEC_REPLACEMENTS, o)
669+
638670
def __init__(self, uri: str) -> None:
639671
if re.search(XsUri._INVALID_URI_REGEX, uri):
640672
raise InvalidUriException(
641673
f"Supplied value '{uri}' does not appear to be a valid URI."
642674
)
643-
self._uri = uri
675+
self._uri = self._spec_migrate(uri)
644676

645677
def __eq__(self, other: Any) -> bool:
646678
if isinstance(other, XsUri):

tests/_data/models.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,31 @@ def get_bom_with_multiple_licenses() -> Bom:
754754
)
755755

756756

757+
def get_bom_for_issue_497_urls() -> Bom:
758+
"""regression test for issue #497
759+
see https://github.com/CycloneDX/cyclonedx-python-lib/issues/497
760+
"""
761+
return _make_bom(components=[
762+
Component(name='dummy', bom_ref='dummy', external_references=[
763+
ExternalReference(
764+
type=ExternalReferenceType.OTHER,
765+
comment='nothing special',
766+
url=XsUri('https://acme.org')
767+
),
768+
ExternalReference(
769+
type=ExternalReferenceType.OTHER,
770+
comment='control characters',
771+
url=XsUri('https://acme.org/?foo=sp ace&bar[23]=42&lt=1<2&gt=3>2&cb={lol}')
772+
),
773+
ExternalReference(
774+
type=ExternalReferenceType.OTHER,
775+
comment='pre-encoded',
776+
url=XsUri('https://acme.org/?bar%5b23%5D=42')
777+
),
778+
])
779+
])
780+
781+
757782
def bom_all_same_bomref() -> Tuple[Bom, int]:
758783
bom = Bom()
759784
bom.metadata.component = Component(name='root', bom_ref='foo', components=[
@@ -774,13 +799,18 @@ def bom_all_same_bomref() -> Tuple[Bom, int]:
774799
if n.startswith('get_bom_') and not n.endswith('_invalid')
775800
)
776801

802+
all_get_bom_funct_valid_immut = tuple(
803+
(n, f) for n, f in getmembers(sys.modules[__name__], isfunction)
804+
if n.startswith('get_bom_') and not n.endswith('_invalid') and not n.endswith('_migrate')
805+
)
806+
777807
all_get_bom_funct_invalid = tuple(
778808
(n, f) for n, f in getmembers(sys.modules[__name__], isfunction)
779809
if n.startswith('get_bom_') and n.endswith('_invalid')
780810
)
781811

782812
all_get_bom_funct_with_incomplete_deps = {
783-
# List of functions that return BOM with an incomplte dependency graph.
813+
# List of functions that return BOM with an incomplete dependency graph.
784814
# It is expected that some process auto-fixes this before actual serialization takes place.
785815
get_bom_just_complete_metadata,
786816
get_bom_with_component_setuptools_basic,
@@ -797,4 +827,5 @@ def bom_all_same_bomref() -> Tuple[Bom, int]:
797827
get_bom_with_services_simple,
798828
get_bom_with_licenses,
799829
get_bom_with_multiple_licenses,
830+
get_bom_for_issue_497_urls,
800831
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" ?>
2+
<bom xmlns="http://cyclonedx.org/schema/bom/1.0" version="1">
3+
<components>
4+
<component type="library">
5+
<name>dummy</name>
6+
<version/>
7+
<modified>false</modified>
8+
</component>
9+
</components>
10+
</bom>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" ?>
2+
<bom xmlns="http://cyclonedx.org/schema/bom/1.1" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
3+
<components>
4+
<component type="library" bom-ref="dummy">
5+
<name>dummy</name>
6+
<version/>
7+
<externalReferences>
8+
<reference type="other">
9+
<url>https://acme.org</url>
10+
<comment>nothing special</comment>
11+
</reference>
12+
<reference type="other">
13+
<url>https://acme.org/?bar%5b23%5D=42</url>
14+
<comment>pre-encoded</comment>
15+
</reference>
16+
<reference type="other">
17+
<url>https://acme.org/?foo=sp%20ace&amp;bar%5B23%5D=42&amp;lt=1%3C2&amp;gt=3%3E2&amp;cb=%7Blol%7D</url>
18+
<comment>control characters</comment>
19+
</reference>
20+
</externalReferences>
21+
</component>
22+
</components>
23+
</bom>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"components": [
3+
{
4+
"bom-ref": "dummy",
5+
"externalReferences": [
6+
{
7+
"comment": "nothing special",
8+
"type": "other",
9+
"url": "https://acme.org"
10+
},
11+
{
12+
"comment": "pre-encoded",
13+
"type": "other",
14+
"url": "https://acme.org/?bar%5b23%5D=42"
15+
},
16+
{
17+
"comment": "control characters",
18+
"type": "other",
19+
"url": "https://acme.org/?foo=sp%20ace&bar%5B23%5D=42&lt=1%3C2&gt=3%3E2&cb=%7Blol%7D"
20+
}
21+
],
22+
"name": "dummy",
23+
"type": "library",
24+
"version": ""
25+
}
26+
],
27+
"dependencies": [
28+
{
29+
"ref": "dummy"
30+
}
31+
],
32+
"metadata": {
33+
"timestamp": "2023-01-07T13:44:32.312678+00:00",
34+
"tools": [
35+
{
36+
"name": "cyclonedx-python-lib",
37+
"vendor": "CycloneDX",
38+
"version": "TESTING"
39+
}
40+
]
41+
},
42+
"serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac",
43+
"version": 1,
44+
"$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json",
45+
"bomFormat": "CycloneDX",
46+
"specVersion": "1.2"
47+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" ?>
2+
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
3+
<metadata>
4+
<timestamp>2023-01-07T13:44:32.312678+00:00</timestamp>
5+
<tools>
6+
<tool>
7+
<vendor>CycloneDX</vendor>
8+
<name>cyclonedx-python-lib</name>
9+
<version>TESTING</version>
10+
</tool>
11+
</tools>
12+
</metadata>
13+
<components>
14+
<component type="library" bom-ref="dummy">
15+
<name>dummy</name>
16+
<version/>
17+
<externalReferences>
18+
<reference type="other">
19+
<url>https://acme.org</url>
20+
<comment>nothing special</comment>
21+
</reference>
22+
<reference type="other">
23+
<url>https://acme.org/?bar%5b23%5D=42</url>
24+
<comment>pre-encoded</comment>
25+
</reference>
26+
<reference type="other">
27+
<url>https://acme.org/?foo=sp%20ace&amp;bar%5B23%5D=42&amp;lt=1%3C2&amp;gt=3%3E2&amp;cb=%7Blol%7D</url>
28+
<comment>control characters</comment>
29+
</reference>
30+
</externalReferences>
31+
</component>
32+
</components>
33+
<dependencies>
34+
<dependency ref="dummy"/>
35+
</dependencies>
36+
</bom>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"components": [
3+
{
4+
"bom-ref": "dummy",
5+
"externalReferences": [
6+
{
7+
"comment": "nothing special",
8+
"type": "other",
9+
"url": "https://acme.org"
10+
},
11+
{
12+
"comment": "pre-encoded",
13+
"type": "other",
14+
"url": "https://acme.org/?bar%5b23%5D=42"
15+
},
16+
{
17+
"comment": "control characters",
18+
"type": "other",
19+
"url": "https://acme.org/?foo=sp%20ace&bar%5B23%5D=42&lt=1%3C2&gt=3%3E2&cb=%7Blol%7D"
20+
}
21+
],
22+
"name": "dummy",
23+
"type": "library",
24+
"version": ""
25+
}
26+
],
27+
"dependencies": [
28+
{
29+
"ref": "dummy"
30+
}
31+
],
32+
"metadata": {
33+
"timestamp": "2023-01-07T13:44:32.312678+00:00",
34+
"tools": [
35+
{
36+
"name": "cyclonedx-python-lib",
37+
"vendor": "CycloneDX",
38+
"version": "TESTING"
39+
}
40+
]
41+
},
42+
"serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac",
43+
"version": 1,
44+
"$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json",
45+
"bomFormat": "CycloneDX",
46+
"specVersion": "1.3"
47+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" ?>
2+
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
3+
<metadata>
4+
<timestamp>2023-01-07T13:44:32.312678+00:00</timestamp>
5+
<tools>
6+
<tool>
7+
<vendor>CycloneDX</vendor>
8+
<name>cyclonedx-python-lib</name>
9+
<version>TESTING</version>
10+
</tool>
11+
</tools>
12+
</metadata>
13+
<components>
14+
<component type="library" bom-ref="dummy">
15+
<name>dummy</name>
16+
<version/>
17+
<externalReferences>
18+
<reference type="other">
19+
<url>https://acme.org</url>
20+
<comment>nothing special</comment>
21+
</reference>
22+
<reference type="other">
23+
<url>https://acme.org/?bar%5b23%5D=42</url>
24+
<comment>pre-encoded</comment>
25+
</reference>
26+
<reference type="other">
27+
<url>https://acme.org/?foo=sp%20ace&amp;bar%5B23%5D=42&amp;lt=1%3C2&amp;gt=3%3E2&amp;cb=%7Blol%7D</url>
28+
<comment>control characters</comment>
29+
</reference>
30+
</externalReferences>
31+
</component>
32+
</components>
33+
<dependencies>
34+
<dependency ref="dummy"/>
35+
</dependencies>
36+
</bom>

0 commit comments

Comments
 (0)