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 []