spdx_tools.spdx.validation.external_package_ref_validator

  1# SPDX-FileCopyrightText: 2022 spdx contributors
  2#
  3# SPDX-License-Identifier: Apache-2.0
  4import re
  5
  6import uritools
  7from beartype.typing import Dict, List
  8
  9from spdx_tools.spdx.model import ExternalPackageRef, ExternalPackageRefCategory
 10from spdx_tools.spdx.model.package import CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES
 11from spdx_tools.spdx.validation.uri_validators import validate_url
 12from spdx_tools.spdx.validation.validation_message import SpdxElementType, ValidationContext, ValidationMessage
 13
 14CPE22TYPE_REGEX = r"^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6}$"
 15CPE23TYPE_REGEX = (
 16    r'^cpe:2\.3:[aho\*\-](:(((\?*|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&\'\(\)\+,\/:;<=>@\[\]\^'
 17    r"`\{\|}~]))+(\?*|\*?))|[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\*\-]))(:(((\?*"
 18    r'|\*?)([a-zA-Z0-9\-\._]|(\\[\\\*\?!"#$$%&\'\(\)\+,\/:;<=>@\[\]\^`\{\|}~]))+(\?*|\*?))|[\*\-])){4}$'
 19)
 20MAVEN_CENTRAL_REGEX = r"^[^:]+:[^:]+(:[^:]+)?$"
 21NPM_REGEX = r"^[^@]+@[^@]+$"
 22NUGET_REGEX = r"^[^/]+/[^/]+$"
 23BOWER_REGEX = r"^[^#]+#[^#]+$"
 24PURL_REGEX = r"^pkg:.+(\/.+)?\/.+(@.+)?(\?.+)?(#.+)?$"
 25SWH_REGEX = r"^swh:1:(snp|rel|rev|dir|cnt):[0-9a-fA-F]{40}$"
 26GITOID_REGEX = r"^gitoid:(blob|tree|commit|tag):(sha1:[0-9a-fA-F]{40}|sha256:[0-9a-fA-F]{64})$"
 27
 28TYPE_TO_REGEX: Dict[str, str] = {
 29    "cpe22Type": CPE22TYPE_REGEX,
 30    "cpe23Type": CPE23TYPE_REGEX,
 31    "maven-central": MAVEN_CENTRAL_REGEX,
 32    "npm": NPM_REGEX,
 33    "nuget": NUGET_REGEX,
 34    "bower": BOWER_REGEX,
 35    "purl": PURL_REGEX,
 36    "swh": SWH_REGEX,
 37    "gitoid": GITOID_REGEX,
 38}
 39
 40
 41def validate_external_package_refs(
 42    external_package_refs: List[ExternalPackageRef], parent_id: str, spdx_version: str
 43) -> List[ValidationMessage]:
 44    validation_messages = []
 45    for external_package_ref in external_package_refs:
 46        validation_messages.extend(validate_external_package_ref(external_package_ref, parent_id, spdx_version))
 47
 48    return validation_messages
 49
 50
 51def validate_external_package_ref(
 52    external_package_ref: ExternalPackageRef, parent_id: str, spdx_version: str
 53) -> List[ValidationMessage]:
 54    validation_messages = []
 55    context = ValidationContext(
 56        parent_id=parent_id, element_type=SpdxElementType.EXTERNAL_PACKAGE_REF, full_element=external_package_ref
 57    )
 58
 59    category = external_package_ref.category
 60    locator = external_package_ref.locator
 61    reference_type = external_package_ref.reference_type
 62
 63    if category == ExternalPackageRefCategory.OTHER:
 64        if " " in locator:
 65            validation_messages.append(
 66                ValidationMessage(
 67                    f"externalPackageRef locator in category OTHER must contain no spaces, but is: {locator}", context
 68                )
 69            )
 70
 71    elif spdx_version == "SPDX-2.2" and reference_type in ["advisory", "fix", "url", "swid"]:
 72        return [ValidationMessage(f'externalPackageRef type "{reference_type}" is not supported in SPDX-2.2', context)]
 73
 74    elif reference_type not in CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]:
 75        validation_messages.append(
 76            ValidationMessage(
 77                f"externalPackageRef type in category {category.name} must be one of "
 78                f"{CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]}, but is: {reference_type}",
 79                context,
 80            )
 81        )
 82
 83    elif reference_type in ["advisory", "fix", "url"]:
 84        if validate_url(locator):
 85            validation_messages.append(
 86                ValidationMessage(
 87                    f'externalPackageRef locator of type "{reference_type}" must be a valid URL, but is: {locator}',
 88                    context,
 89                )
 90            )
 91
 92    elif reference_type == "swid":
 93        if not uritools.isuri(locator) or not locator.startswith("swid"):
 94            validation_messages.append(
 95                ValidationMessage(
 96                    f'externalPackageRef locator of type "swid" must be a valid URI with scheme swid, '
 97                    f"but is: {locator}",
 98                    context,
 99                )
100            )
101
102    else:
103        validation_messages.extend(validate_against_regex(locator, reference_type, context))
104
105    return validation_messages
106
107
108def validate_against_regex(
109    string_to_validate: str, reference_type: str, context: ValidationContext
110) -> List[ValidationMessage]:
111    regex = TYPE_TO_REGEX[reference_type]
112    if not re.match(regex, string_to_validate):
113        return [
114            ValidationMessage(
115                f'externalPackageRef locator of type "{reference_type}" must conform with the regex {regex}, '
116                f"but is: {string_to_validate}",
117                context,
118            )
119        ]
120    return []
CPE22TYPE_REGEX = '^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\\-~%]*){0,6}$'
CPE23TYPE_REGEX = '^cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!"#$$%&\\\'\\(\\)\\+,\\/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!"#$$%&\\\'\\(\\)\\+,\\/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4}$'
MAVEN_CENTRAL_REGEX = '^[^:]+:[^:]+(:[^:]+)?$'
NPM_REGEX = '^[^@]+@[^@]+$'
NUGET_REGEX = '^[^/]+/[^/]+$'
BOWER_REGEX = '^[^#]+#[^#]+$'
PURL_REGEX = '^pkg:.+(\\/.+)?\\/.+(@.+)?(\\?.+)?(#.+)?$'
SWH_REGEX = '^swh:1:(snp|rel|rev|dir|cnt):[0-9a-fA-F]{40}$'
GITOID_REGEX = '^gitoid:(blob|tree|commit|tag):(sha1:[0-9a-fA-F]{40}|sha256:[0-9a-fA-F]{64})$'
TYPE_TO_REGEX: dict[str, str] = {'cpe22Type': '^c[pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\\-~%]*){0,6}$', 'cpe23Type': '^cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!"#$$%&\\\'\\(\\)\\+,\\/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!"#$$%&\\\'\\(\\)\\+,\\/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4}$', 'maven-central': '^[^:]+:[^:]+(:[^:]+)?$', 'npm': '^[^@]+@[^@]+$', 'nuget': '^[^/]+/[^/]+$', 'bower': '^[^#]+#[^#]+$', 'purl': '^pkg:.+(\\/.+)?\\/.+(@.+)?(\\?.+)?(#.+)?$', 'swh': '^swh:1:(snp|rel|rev|dir|cnt):[0-9a-fA-F]{40}$', 'gitoid': '^gitoid:(blob|tree|commit|tag):(sha1:[0-9a-fA-F]{40}|sha256:[0-9a-fA-F]{64})$'}
def validate_external_package_refs( external_package_refs: list[spdx_tools.spdx.model.package.ExternalPackageRef], parent_id: str, spdx_version: str) -> list[spdx_tools.spdx.validation.validation_message.ValidationMessage]:
42def validate_external_package_refs(
43    external_package_refs: List[ExternalPackageRef], parent_id: str, spdx_version: str
44) -> List[ValidationMessage]:
45    validation_messages = []
46    for external_package_ref in external_package_refs:
47        validation_messages.extend(validate_external_package_ref(external_package_ref, parent_id, spdx_version))
48
49    return validation_messages
def validate_external_package_ref( external_package_ref: spdx_tools.spdx.model.package.ExternalPackageRef, parent_id: str, spdx_version: str) -> list[spdx_tools.spdx.validation.validation_message.ValidationMessage]:
 52def validate_external_package_ref(
 53    external_package_ref: ExternalPackageRef, parent_id: str, spdx_version: str
 54) -> List[ValidationMessage]:
 55    validation_messages = []
 56    context = ValidationContext(
 57        parent_id=parent_id, element_type=SpdxElementType.EXTERNAL_PACKAGE_REF, full_element=external_package_ref
 58    )
 59
 60    category = external_package_ref.category
 61    locator = external_package_ref.locator
 62    reference_type = external_package_ref.reference_type
 63
 64    if category == ExternalPackageRefCategory.OTHER:
 65        if " " in locator:
 66            validation_messages.append(
 67                ValidationMessage(
 68                    f"externalPackageRef locator in category OTHER must contain no spaces, but is: {locator}", context
 69                )
 70            )
 71
 72    elif spdx_version == "SPDX-2.2" and reference_type in ["advisory", "fix", "url", "swid"]:
 73        return [ValidationMessage(f'externalPackageRef type "{reference_type}" is not supported in SPDX-2.2', context)]
 74
 75    elif reference_type not in CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]:
 76        validation_messages.append(
 77            ValidationMessage(
 78                f"externalPackageRef type in category {category.name} must be one of "
 79                f"{CATEGORY_TO_EXTERNAL_PACKAGE_REF_TYPES[category]}, but is: {reference_type}",
 80                context,
 81            )
 82        )
 83
 84    elif reference_type in ["advisory", "fix", "url"]:
 85        if validate_url(locator):
 86            validation_messages.append(
 87                ValidationMessage(
 88                    f'externalPackageRef locator of type "{reference_type}" must be a valid URL, but is: {locator}',
 89                    context,
 90                )
 91            )
 92
 93    elif reference_type == "swid":
 94        if not uritools.isuri(locator) or not locator.startswith("swid"):
 95            validation_messages.append(
 96                ValidationMessage(
 97                    f'externalPackageRef locator of type "swid" must be a valid URI with scheme swid, '
 98                    f"but is: {locator}",
 99                    context,
100                )
101            )
102
103    else:
104        validation_messages.extend(validate_against_regex(locator, reference_type, context))
105
106    return validation_messages
def validate_against_regex( string_to_validate: str, reference_type: str, context: spdx_tools.spdx.validation.validation_message.ValidationContext) -> list[spdx_tools.spdx.validation.validation_message.ValidationMessage]:
109def validate_against_regex(
110    string_to_validate: str, reference_type: str, context: ValidationContext
111) -> List[ValidationMessage]:
112    regex = TYPE_TO_REGEX[reference_type]
113    if not re.match(regex, string_to_validate):
114        return [
115            ValidationMessage(
116                f'externalPackageRef locator of type "{reference_type}" must conform with the regex {regex}, '
117                f"but is: {string_to_validate}",
118                context,
119            )
120        ]
121    return []