github.com/anchore/syft@v1.38.2/syft/format/internal/spdxutil/helpers/license.go (about)

     1  package helpers
     2  
     3  import (
     4  	"sort"
     5  	"strings"
     6  
     7  	"github.com/spdx/tools-golang/spdx"
     8  
     9  	"github.com/anchore/syft/internal/spdxlicense"
    10  	"github.com/anchore/syft/syft/license"
    11  	"github.com/anchore/syft/syft/pkg"
    12  )
    13  
    14  func License(p pkg.Package) (concluded, declared string, otherLicenses []spdx.OtherLicense) {
    15  	// source: https://spdx.github.io/spdx-spec/v2.3/package-information/#713-concluded-license-field
    16  	// The options to populate this field are limited to:
    17  	// A valid SPDX License Expression as defined in Annex D;
    18  	// NONE, if the SPDX file creator concludes there is no license available for this package; or
    19  	// NOASSERTION if:
    20  	//   (i) the SPDX file creator has attempted to but cannot reach a reasonable objective determination;
    21  	//   (ii) the SPDX file creator has made no attempt to determine this field; or
    22  	//   (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
    23  
    24  	if p.Licenses.Empty() {
    25  		return NOASSERTION, NOASSERTION, nil
    26  	}
    27  
    28  	// take all licenses and assume an AND expression;
    29  	// for information about license expressions see:
    30  	// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
    31  	pc, pd, ol := ParseLicenses(p.Licenses.ToSlice())
    32  
    33  	return joinLicenses(pc), joinLicenses(pd), ol
    34  }
    35  
    36  func joinLicenses(licenses []SPDXLicense) string {
    37  	if len(licenses) == 0 {
    38  		return NOASSERTION
    39  	}
    40  
    41  	var newLicenses []string
    42  
    43  	for _, l := range licenses {
    44  		v := l.ID
    45  		// check if license does not start or end with parens
    46  		if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
    47  			// if license contains AND, OR, or WITH, then wrap in parens
    48  			if strings.Contains(v, " AND ") ||
    49  				strings.Contains(v, " OR ") ||
    50  				strings.Contains(v, " WITH ") {
    51  				newLicenses = append(newLicenses, "("+v+")")
    52  				continue
    53  			}
    54  		}
    55  		newLicenses = append(newLicenses, v)
    56  	}
    57  
    58  	return strings.Join(newLicenses, " AND ")
    59  }
    60  
    61  type SPDXLicense struct {
    62  	// Valid SPDX ID OR License Value (should have LicenseRef- prefix and be sanitized)
    63  	// OR combination of the above as a valid SPDX License Expression as defined in Annex D.
    64  	// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
    65  	ID string
    66  	// If the SPDX license is not on the SPDX License List
    67  	LicenseName string
    68  	FullText    string // 0..1 (Mandatory, one) if there is a License Identifier assigned (LicenseRef).
    69  	URLs        []string
    70  }
    71  
    72  func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense, otherLicenses []spdx.OtherLicense) {
    73  	for _, l := range raw {
    74  		candidate := createSPDXLicense(l)
    75  
    76  		// this determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
    77  		// of the SPDX spec, where:
    78  		// - we should not have a complex SPDX expression
    79  		// - if a single license, it should not be a known license (on the SPDX license list)
    80  		if l.SPDXExpression == "" && strings.Contains(candidate.ID, spdxlicense.LicenseRefPrefix) {
    81  			otherLicenses = append(otherLicenses, spdx.OtherLicense{
    82  				LicenseIdentifier:      candidate.ID,
    83  				ExtractedText:          candidate.FullText,
    84  				LicenseName:            candidate.LicenseName,
    85  				LicenseCrossReferences: candidate.URLs,
    86  			})
    87  		}
    88  		switch l.Type {
    89  		case license.Concluded:
    90  			concluded = append(concluded, candidate)
    91  		case license.Declared:
    92  			declared = append(declared, candidate)
    93  		}
    94  	}
    95  
    96  	return concluded, declared, otherLicenses
    97  }
    98  
    99  func createSPDXLicense(l pkg.License) SPDXLicense {
   100  	// source: https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#102-extracted-text-field
   101  	// we need to populate this field in the spdx document if we have a license ref
   102  	// 0..1 (Mandatory, one) if there is a License Identifier assigned (LicenseRef).
   103  	ft := NOASSERTION
   104  	if l.Contents != "" {
   105  		ft = l.Contents
   106  	}
   107  
   108  	return SPDXLicense{
   109  		ID:          generateLicenseID(l),
   110  		LicenseName: l.Value,
   111  		FullText:    ft,
   112  		URLs:        l.URLs,
   113  	}
   114  }
   115  
   116  // generateLicenseID generates a license ID for the given license, which is either the license value or the SPDX expression.
   117  func generateLicenseID(l pkg.License) string {
   118  	if l.SPDXExpression != "" {
   119  		return l.SPDXExpression
   120  	}
   121  
   122  	// syft format includes the algo for the sha in the values
   123  	// we can strip this and just make LicenseRef-<sum> for spdx consumption
   124  	id := strings.ReplaceAll(l.Value, "sha256:", "")
   125  	if !strings.HasPrefix(id, "LicenseRef-") {
   126  		id = "LicenseRef-" + id
   127  	}
   128  	return SanitizeElementID(id)
   129  }
   130  
   131  type SPDXOtherLicenseSet struct {
   132  	set map[string]spdx.OtherLicense
   133  }
   134  
   135  func NewSPDXOtherLicenseSet() *SPDXOtherLicenseSet {
   136  	return &SPDXOtherLicenseSet{
   137  		set: make(map[string]spdx.OtherLicense),
   138  	}
   139  }
   140  
   141  func (s *SPDXOtherLicenseSet) Add(licenses ...spdx.OtherLicense) {
   142  	for _, l := range licenses {
   143  		s.set[l.LicenseIdentifier] = l
   144  	}
   145  }
   146  
   147  type ByLicenseIdentifier []spdx.OtherLicense
   148  
   149  func (o ByLicenseIdentifier) Len() int      { return len(o) }
   150  func (o ByLicenseIdentifier) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
   151  func (o ByLicenseIdentifier) Less(i, j int) bool {
   152  	return o[i].LicenseIdentifier < o[j].LicenseIdentifier
   153  }
   154  
   155  func (s *SPDXOtherLicenseSet) ToSlice() []spdx.OtherLicense {
   156  	values := make([]spdx.OtherLicense, 0, len(s.set))
   157  	for _, v := range s.set {
   158  		values = append(values, v)
   159  	}
   160  	sort.Sort(ByLicenseIdentifier(values))
   161  	return values
   162  }