1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun"""Code for parsing OpenEmbedded license strings""" 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunimport ast 7*4882a593Smuzhiyunimport re 8*4882a593Smuzhiyunfrom fnmatch import fnmatchcase as fnmatch 9*4882a593Smuzhiyun 10*4882a593Smuzhiyundef license_ok(license, dont_want_licenses): 11*4882a593Smuzhiyun """ Return False if License exist in dont_want_licenses else True """ 12*4882a593Smuzhiyun for dwl in dont_want_licenses: 13*4882a593Smuzhiyun if fnmatch(license, dwl): 14*4882a593Smuzhiyun return False 15*4882a593Smuzhiyun return True 16*4882a593Smuzhiyun 17*4882a593Smuzhiyundef obsolete_license_list(): 18*4882a593Smuzhiyun return ["AGPL-3", "AGPL-3+", "AGPLv3", "AGPLv3+", "AGPLv3.0", "AGPLv3.0+", "AGPL-3.0", "AGPL-3.0+", "BSD-0-Clause", 19*4882a593Smuzhiyun "GPL-1", "GPL-1+", "GPLv1", "GPLv1+", "GPLv1.0", "GPLv1.0+", "GPL-1.0", "GPL-1.0+", "GPL-2", "GPL-2+", "GPLv2", 20*4882a593Smuzhiyun "GPLv2+", "GPLv2.0", "GPLv2.0+", "GPL-2.0", "GPL-2.0+", "GPL-3", "GPL-3+", "GPLv3", "GPLv3+", "GPLv3.0", "GPLv3.0+", 21*4882a593Smuzhiyun "GPL-3.0", "GPL-3.0+", "LGPLv2", "LGPLv2+", "LGPLv2.0", "LGPLv2.0+", "LGPL-2.0", "LGPL-2.0+", "LGPL2.1", "LGPL2.1+", 22*4882a593Smuzhiyun "LGPLv2.1", "LGPLv2.1+", "LGPL-2.1", "LGPL-2.1+", "LGPLv3", "LGPLv3+", "LGPL-3.0", "LGPL-3.0+", "MPL-1", "MPLv1", 23*4882a593Smuzhiyun "MPLv1.1", "MPLv2", "MIT-X", "MIT-style", "openssl", "PSF", "PSFv2", "Python-2", "Apachev2", "Apache-2", "Artisticv1", 24*4882a593Smuzhiyun "Artistic-1", "AFL-2", "AFL-1", "AFLv2", "AFLv1", "CDDLv1", "CDDL-1", "EPLv1.0", "FreeType", "Nauman", 25*4882a593Smuzhiyun "tcl", "vim", "SGIv1"] 26*4882a593Smuzhiyun 27*4882a593Smuzhiyunclass LicenseError(Exception): 28*4882a593Smuzhiyun pass 29*4882a593Smuzhiyun 30*4882a593Smuzhiyunclass LicenseSyntaxError(LicenseError): 31*4882a593Smuzhiyun def __init__(self, licensestr, exc): 32*4882a593Smuzhiyun self.licensestr = licensestr 33*4882a593Smuzhiyun self.exc = exc 34*4882a593Smuzhiyun LicenseError.__init__(self) 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun def __str__(self): 37*4882a593Smuzhiyun return "error in '%s': %s" % (self.licensestr, self.exc) 38*4882a593Smuzhiyun 39*4882a593Smuzhiyunclass InvalidLicense(LicenseError): 40*4882a593Smuzhiyun def __init__(self, license): 41*4882a593Smuzhiyun self.license = license 42*4882a593Smuzhiyun LicenseError.__init__(self) 43*4882a593Smuzhiyun 44*4882a593Smuzhiyun def __str__(self): 45*4882a593Smuzhiyun return "invalid characters in license '%s'" % self.license 46*4882a593Smuzhiyun 47*4882a593Smuzhiyunlicense_operator_chars = '&|() ' 48*4882a593Smuzhiyunlicense_operator = re.compile(r'([' + license_operator_chars + '])') 49*4882a593Smuzhiyunlicense_pattern = re.compile(r'[a-zA-Z0-9.+_\-]+$') 50*4882a593Smuzhiyun 51*4882a593Smuzhiyunclass LicenseVisitor(ast.NodeVisitor): 52*4882a593Smuzhiyun """Get elements based on OpenEmbedded license strings""" 53*4882a593Smuzhiyun def get_elements(self, licensestr): 54*4882a593Smuzhiyun new_elements = [] 55*4882a593Smuzhiyun elements = list([x for x in license_operator.split(licensestr) if x.strip()]) 56*4882a593Smuzhiyun for pos, element in enumerate(elements): 57*4882a593Smuzhiyun if license_pattern.match(element): 58*4882a593Smuzhiyun if pos > 0 and license_pattern.match(elements[pos-1]): 59*4882a593Smuzhiyun new_elements.append('&') 60*4882a593Smuzhiyun element = '"' + element + '"' 61*4882a593Smuzhiyun elif not license_operator.match(element): 62*4882a593Smuzhiyun raise InvalidLicense(element) 63*4882a593Smuzhiyun new_elements.append(element) 64*4882a593Smuzhiyun 65*4882a593Smuzhiyun return new_elements 66*4882a593Smuzhiyun 67*4882a593Smuzhiyun """Syntax tree visitor which can accept elements previously generated with 68*4882a593Smuzhiyun OpenEmbedded license string""" 69*4882a593Smuzhiyun def visit_elements(self, elements): 70*4882a593Smuzhiyun self.visit(ast.parse(' '.join(elements))) 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun """Syntax tree visitor which can accept OpenEmbedded license strings""" 73*4882a593Smuzhiyun def visit_string(self, licensestr): 74*4882a593Smuzhiyun self.visit_elements(self.get_elements(licensestr)) 75*4882a593Smuzhiyun 76*4882a593Smuzhiyunclass FlattenVisitor(LicenseVisitor): 77*4882a593Smuzhiyun """Flatten a license tree (parsed from a string) by selecting one of each 78*4882a593Smuzhiyun set of OR options, in the way the user specifies""" 79*4882a593Smuzhiyun def __init__(self, choose_licenses): 80*4882a593Smuzhiyun self.choose_licenses = choose_licenses 81*4882a593Smuzhiyun self.licenses = [] 82*4882a593Smuzhiyun LicenseVisitor.__init__(self) 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun def visit_Str(self, node): 85*4882a593Smuzhiyun self.licenses.append(node.s) 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun def visit_Constant(self, node): 88*4882a593Smuzhiyun self.licenses.append(node.value) 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun def visit_BinOp(self, node): 91*4882a593Smuzhiyun if isinstance(node.op, ast.BitOr): 92*4882a593Smuzhiyun left = FlattenVisitor(self.choose_licenses) 93*4882a593Smuzhiyun left.visit(node.left) 94*4882a593Smuzhiyun 95*4882a593Smuzhiyun right = FlattenVisitor(self.choose_licenses) 96*4882a593Smuzhiyun right.visit(node.right) 97*4882a593Smuzhiyun 98*4882a593Smuzhiyun selected = self.choose_licenses(left.licenses, right.licenses) 99*4882a593Smuzhiyun self.licenses.extend(selected) 100*4882a593Smuzhiyun else: 101*4882a593Smuzhiyun self.generic_visit(node) 102*4882a593Smuzhiyun 103*4882a593Smuzhiyundef flattened_licenses(licensestr, choose_licenses): 104*4882a593Smuzhiyun """Given a license string and choose_licenses function, return a flat list of licenses""" 105*4882a593Smuzhiyun flatten = FlattenVisitor(choose_licenses) 106*4882a593Smuzhiyun try: 107*4882a593Smuzhiyun flatten.visit_string(licensestr) 108*4882a593Smuzhiyun except SyntaxError as exc: 109*4882a593Smuzhiyun raise LicenseSyntaxError(licensestr, exc) 110*4882a593Smuzhiyun return flatten.licenses 111*4882a593Smuzhiyun 112*4882a593Smuzhiyundef is_included(licensestr, include_licenses=None, exclude_licenses=None): 113*4882a593Smuzhiyun """Given a license string, a list of licenses to include and a list of 114*4882a593Smuzhiyun licenses to exclude, determine if the license string matches the include 115*4882a593Smuzhiyun list and does not match the exclude list. 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun Returns a tuple holding the boolean state and a list of the applicable 118*4882a593Smuzhiyun licenses that were excluded if state is False, or the licenses that were 119*4882a593Smuzhiyun included if the state is True.""" 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun def include_license(license): 122*4882a593Smuzhiyun return any(fnmatch(license, pattern) for pattern in include_licenses) 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun def exclude_license(license): 125*4882a593Smuzhiyun return any(fnmatch(license, pattern) for pattern in exclude_licenses) 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun def choose_licenses(alpha, beta): 128*4882a593Smuzhiyun """Select the option in an OR which is the 'best' (has the most 129*4882a593Smuzhiyun included licenses and no excluded licenses).""" 130*4882a593Smuzhiyun # The factor 1000 below is arbitrary, just expected to be much larger 131*4882a593Smuzhiyun # than the number of licenses actually specified. That way the weight 132*4882a593Smuzhiyun # will be negative if the list of licenses contains an excluded license, 133*4882a593Smuzhiyun # but still gives a higher weight to the list with the most included 134*4882a593Smuzhiyun # licenses. 135*4882a593Smuzhiyun alpha_weight = (len(list(filter(include_license, alpha))) - 136*4882a593Smuzhiyun 1000 * (len(list(filter(exclude_license, alpha))) > 0)) 137*4882a593Smuzhiyun beta_weight = (len(list(filter(include_license, beta))) - 138*4882a593Smuzhiyun 1000 * (len(list(filter(exclude_license, beta))) > 0)) 139*4882a593Smuzhiyun if alpha_weight >= beta_weight: 140*4882a593Smuzhiyun return alpha 141*4882a593Smuzhiyun else: 142*4882a593Smuzhiyun return beta 143*4882a593Smuzhiyun 144*4882a593Smuzhiyun if not include_licenses: 145*4882a593Smuzhiyun include_licenses = ['*'] 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun if not exclude_licenses: 148*4882a593Smuzhiyun exclude_licenses = [] 149*4882a593Smuzhiyun 150*4882a593Smuzhiyun licenses = flattened_licenses(licensestr, choose_licenses) 151*4882a593Smuzhiyun excluded = [lic for lic in licenses if exclude_license(lic)] 152*4882a593Smuzhiyun included = [lic for lic in licenses if include_license(lic)] 153*4882a593Smuzhiyun if excluded: 154*4882a593Smuzhiyun return False, excluded 155*4882a593Smuzhiyun else: 156*4882a593Smuzhiyun return True, included 157*4882a593Smuzhiyun 158*4882a593Smuzhiyunclass ManifestVisitor(LicenseVisitor): 159*4882a593Smuzhiyun """Walk license tree (parsed from a string) removing the incompatible 160*4882a593Smuzhiyun licenses specified""" 161*4882a593Smuzhiyun def __init__(self, dont_want_licenses, canonical_license, d): 162*4882a593Smuzhiyun self._dont_want_licenses = dont_want_licenses 163*4882a593Smuzhiyun self._canonical_license = canonical_license 164*4882a593Smuzhiyun self._d = d 165*4882a593Smuzhiyun self._operators = [] 166*4882a593Smuzhiyun 167*4882a593Smuzhiyun self.licenses = [] 168*4882a593Smuzhiyun self.licensestr = '' 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun LicenseVisitor.__init__(self) 171*4882a593Smuzhiyun 172*4882a593Smuzhiyun def visit(self, node): 173*4882a593Smuzhiyun if isinstance(node, ast.Str): 174*4882a593Smuzhiyun lic = node.s 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun if license_ok(self._canonical_license(self._d, lic), 177*4882a593Smuzhiyun self._dont_want_licenses) == True: 178*4882a593Smuzhiyun if self._operators: 179*4882a593Smuzhiyun ops = [] 180*4882a593Smuzhiyun for op in self._operators: 181*4882a593Smuzhiyun if op == '[': 182*4882a593Smuzhiyun ops.append(op) 183*4882a593Smuzhiyun elif op == ']': 184*4882a593Smuzhiyun ops.append(op) 185*4882a593Smuzhiyun else: 186*4882a593Smuzhiyun if not ops: 187*4882a593Smuzhiyun ops.append(op) 188*4882a593Smuzhiyun elif ops[-1] in ['[', ']']: 189*4882a593Smuzhiyun ops.append(op) 190*4882a593Smuzhiyun else: 191*4882a593Smuzhiyun ops[-1] = op 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun for op in ops: 194*4882a593Smuzhiyun if op == '[' or op == ']': 195*4882a593Smuzhiyun self.licensestr += op 196*4882a593Smuzhiyun elif self.licenses: 197*4882a593Smuzhiyun self.licensestr += ' ' + op + ' ' 198*4882a593Smuzhiyun 199*4882a593Smuzhiyun self._operators = [] 200*4882a593Smuzhiyun 201*4882a593Smuzhiyun self.licensestr += lic 202*4882a593Smuzhiyun self.licenses.append(lic) 203*4882a593Smuzhiyun elif isinstance(node, ast.BitAnd): 204*4882a593Smuzhiyun self._operators.append("&") 205*4882a593Smuzhiyun elif isinstance(node, ast.BitOr): 206*4882a593Smuzhiyun self._operators.append("|") 207*4882a593Smuzhiyun elif isinstance(node, ast.List): 208*4882a593Smuzhiyun self._operators.append("[") 209*4882a593Smuzhiyun elif isinstance(node, ast.Load): 210*4882a593Smuzhiyun self.licensestr += "]" 211*4882a593Smuzhiyun 212*4882a593Smuzhiyun self.generic_visit(node) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyundef manifest_licenses(licensestr, dont_want_licenses, canonical_license, d): 215*4882a593Smuzhiyun """Given a license string and dont_want_licenses list, 216*4882a593Smuzhiyun return license string filtered and a list of licenses""" 217*4882a593Smuzhiyun manifest = ManifestVisitor(dont_want_licenses, canonical_license, d) 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun try: 220*4882a593Smuzhiyun elements = manifest.get_elements(licensestr) 221*4882a593Smuzhiyun 222*4882a593Smuzhiyun # Replace '()' to '[]' for handle in ast as List and Load types. 223*4882a593Smuzhiyun elements = ['[' if e == '(' else e for e in elements] 224*4882a593Smuzhiyun elements = [']' if e == ')' else e for e in elements] 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun manifest.visit_elements(elements) 227*4882a593Smuzhiyun except SyntaxError as exc: 228*4882a593Smuzhiyun raise LicenseSyntaxError(licensestr, exc) 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun # Replace '[]' to '()' for output correct license. 231*4882a593Smuzhiyun manifest.licensestr = manifest.licensestr.replace('[', '(').replace(']', ')') 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun return (manifest.licensestr, manifest.licenses) 234*4882a593Smuzhiyun 235*4882a593Smuzhiyunclass ListVisitor(LicenseVisitor): 236*4882a593Smuzhiyun """Record all different licenses found in the license string""" 237*4882a593Smuzhiyun def __init__(self): 238*4882a593Smuzhiyun self.licenses = set() 239*4882a593Smuzhiyun 240*4882a593Smuzhiyun def visit_Str(self, node): 241*4882a593Smuzhiyun self.licenses.add(node.s) 242*4882a593Smuzhiyun 243*4882a593Smuzhiyun def visit_Constant(self, node): 244*4882a593Smuzhiyun self.licenses.add(node.value) 245*4882a593Smuzhiyun 246*4882a593Smuzhiyundef list_licenses(licensestr): 247*4882a593Smuzhiyun """Simply get a list of all licenses mentioned in a license string. 248*4882a593Smuzhiyun Binary operators are not applied or taken into account in any way""" 249*4882a593Smuzhiyun visitor = ListVisitor() 250*4882a593Smuzhiyun try: 251*4882a593Smuzhiyun visitor.visit_string(licensestr) 252*4882a593Smuzhiyun except SyntaxError as exc: 253*4882a593Smuzhiyun raise LicenseSyntaxError(licensestr, exc) 254*4882a593Smuzhiyun return visitor.licenses 255*4882a593Smuzhiyun 256*4882a593Smuzhiyundef apply_pkg_license_exception(pkg, bad_licenses, exceptions): 257*4882a593Smuzhiyun """Return remaining bad licenses after removing any package exceptions""" 258*4882a593Smuzhiyun 259*4882a593Smuzhiyun return [lic for lic in bad_licenses if pkg + ':' + lic not in exceptions] 260