├── CODEOWNERS ├── script └── bootstrap ├── .github ├── dependabot.yml └── workflows │ ├── test.yaml │ ├── lint.yaml │ └── fetch-licenses.yaml ├── spdxexp ├── doc.go ├── spdxlicenses │ ├── doc.go │ ├── get_deprecated.go │ ├── get_exceptions.go │ ├── license_ranges.go │ └── get_licenses.go ├── extracts.go ├── helpers.go ├── test_helper.go ├── extracts_test.go ├── license.go ├── compare.go ├── license_test.go ├── compare_test.go ├── node_test.go ├── scan.go ├── node.go ├── satisfies.go ├── parse.go └── scan_test.go ├── .gitignore ├── cmd ├── deprecated_license_ids.json ├── deprecated_license_ids.txt ├── doc.go ├── exception_ids.json ├── exception_ids.txt ├── exceptions.go ├── main.go ├── license.go ├── license_ids.json ├── license_ids.txt └── exceptions.json ├── go.mod ├── SUPPORT.md ├── LICENSE ├── .golangci.yaml ├── SECURITY.md ├── CONTRIBUTING.md ├── go.sum ├── Makefile ├── CODE_OF_CONDUCT.md └── README.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This repository is maintained by: 2 | * @elrayle @ajhenry @dangoor 3 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # download dependencies 4 | go mod download -json 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | -------------------------------------------------------------------------------- /spdxexp/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Spdxexp package validates licenses and determines if a license expression is satisfied by a list of licenses. 3 | Validity of a license is determined by the [SPDX license list]. 4 | 5 | [SPDX license list]: https://spdx.org/licenses/ 6 | */ 7 | package spdxexp 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Ignore path setup by fetch-licenses workflow 18 | official-spdx-licenses/ 19 | -------------------------------------------------------------------------------- /cmd/deprecated_license_ids.json: -------------------------------------------------------------------------------- 1 | ["AGPL-1.0","AGPL-3.0","BSD-2-Clause-FreeBSD","BSD-2-Clause-NetBSD","bzip2-1.0.5","eCos-2.0","GFDL-1.1","GFDL-1.2","GFDL-1.3","GPL-1.0","GPL-1.0+","GPL-2.0","GPL-2.0+","GPL-2.0-with-autoconf-exception","GPL-2.0-with-bison-exception","GPL-2.0-with-classpath-exception","GPL-2.0-with-font-exception","GPL-2.0-with-GCC-exception","GPL-3.0","GPL-3.0+","GPL-3.0-with-autoconf-exception","GPL-3.0-with-GCC-exception","LGPL-2.0","LGPL-2.0+","LGPL-2.1","LGPL-2.1+","LGPL-3.0","LGPL-3.0+","Nunit","StandardML-NJ","wxWindows"] -------------------------------------------------------------------------------- /spdxexp/spdxlicenses/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Spdxlicenses package provides functions to get licenses, deprecated licenses, and exceptions. These are auto-generated and should not be modified directly. 3 | Licenses are generated from the [SPDX official machine readable license list]. 4 | 5 | In addition, this package includes a function to return license ranges for sequential licenses and ranges including modifiers (i.e. -only, -or-later). 6 | 7 | [SPDX official machine readable license list]: https://github.com/spdx/license-list-data 8 | */ 9 | package spdxlicenses 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/github/go-spdx/v2 2 | 3 | retract v2.3.0 // Compatibility issues with go 1.22 4 | 5 | go 1.24 6 | 7 | require github.com/stretchr/testify v1.8.1 8 | 9 | require ( 10 | github.com/davecgh/go-spew v1.1.1 // indirect 11 | github.com/pmezard/go-difflib v1.0.0 // indirect 12 | github.com/yuin/goldmark v1.7.13 // indirect 13 | golang.org/x/mod v0.27.0 // indirect 14 | golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f // indirect 15 | golang.org/x/tools/cmd/godoc v0.1.0-deprecated // indirect 16 | golang.org/x/tools/godoc v0.1.0-deprecated // indirect 17 | gopkg.in/yaml.v3 v3.0.1 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: "Build and test" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: {} 7 | workflow_dispatch: # Sometimes tests get stuck in PRs; This allows them to be rerun manually 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | fail-fast: false 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v6 20 | - uses: actions/setup-go@v6 21 | with: 22 | go-version: "1.18.5" 23 | 24 | - name: Build program 25 | run: go build ./... 26 | 27 | - name: Run tests 28 | run: go test -race ./... 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: "Lint" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: {} 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: actions/setup-go@v6 20 | with: 21 | go-version: "1.18.5" 22 | 23 | - name: golangci-lint 24 | uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 25 | with: 26 | version: v1.47.3 27 | args: --verbose --config .golangci.yaml 28 | skip-cache: true 29 | -------------------------------------------------------------------------------- /spdxexp/extracts.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | // ExtractLicenses extracts licenses from the given expression without duplicates. 4 | // Returns an array of licenses or error if error occurs during processing. 5 | func ExtractLicenses(expression string) ([]string, error) { 6 | node, err := parse(expression) 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | expanded := node.expand(true) 12 | licenses := make([]string, 0) 13 | allLicenses := flatten(expanded) 14 | for _, licenseNode := range allLicenses { 15 | licenses = append(licenses, *licenseNode.reconstructedLicenseString()) 16 | } 17 | 18 | licenses = removeDuplicateStrings(licenses) 19 | 20 | return licenses, nil 21 | } 22 | -------------------------------------------------------------------------------- /spdxexp/helpers.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | // flatten will take an array of nested array and return 4 | // all nested elements in an array. e.g. [[1,2,[3]],4] -> [1,2,3,4] 5 | func flatten[T any](lists [][]T) []T { 6 | var res []T 7 | for _, list := range lists { 8 | res = append(res, list...) 9 | } 10 | return res 11 | } 12 | 13 | // removeDuplicateStrings will remove all duplicates from a slice 14 | func removeDuplicateStrings(sliceList []string) []string { 15 | allKeys := make(map[string]bool) 16 | list := []string{} 17 | for _, item := range sliceList { 18 | if _, value := allKeys[item]; !value { 19 | allKeys[item] = true 20 | list = append(list, item) 21 | } 22 | } 23 | return list 24 | } 25 | -------------------------------------------------------------------------------- /cmd/deprecated_license_ids.txt: -------------------------------------------------------------------------------- 1 | "AGPL-1.0", 2 | "AGPL-3.0", 3 | "BSD-2-Clause-FreeBSD", 4 | "BSD-2-Clause-NetBSD", 5 | "bzip2-1.0.5", 6 | "eCos-2.0", 7 | "GFDL-1.1", 8 | "GFDL-1.2", 9 | "GFDL-1.3", 10 | "GPL-1.0", 11 | "GPL-1.0+", 12 | "GPL-2.0", 13 | "GPL-2.0+", 14 | "GPL-2.0-with-autoconf-exception", 15 | "GPL-2.0-with-bison-exception", 16 | "GPL-2.0-with-classpath-exception", 17 | "GPL-2.0-with-font-exception", 18 | "GPL-2.0-with-GCC-exception", 19 | "GPL-3.0", 20 | "GPL-3.0+", 21 | "GPL-3.0-with-autoconf-exception", 22 | "GPL-3.0-with-GCC-exception", 23 | "LGPL-2.0", 24 | "LGPL-2.0+", 25 | "LGPL-2.1", 26 | "LGPL-2.1+", 27 | "LGPL-3.0", 28 | "LGPL-3.0+", 29 | "Nunit", 30 | "StandardML-NJ", 31 | "wxWindows", 32 | -------------------------------------------------------------------------------- /cmd/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Extracts license, deprecation, and exception ids from the official spdx license list data. 3 | The source data needs to be manually updated by copying the licenses.json file from 4 | https://github.com/spdx/license-list-data/blob/main/json/licenses.json and exceptions.json 5 | file from https://github.com/spdx/license-list-data/blob/main/json/exceptions.json. 6 | 7 | After running the extract command, the license_ids.json, deprecated_ids.json, and exception_ids.json 8 | files will be overwritten with the extracted ids. These license ids can then be used to update the 9 | spdxexp/license.go file. 10 | 11 | Command to run all extractions (run command from the /cmd directory): 12 | 13 | go run . extract -l -e 14 | 15 | Usage options: 16 | 17 | -h: prints this help message 18 | -l: Extract license ids 19 | -e: Extract exception ids 20 | */ 21 | package main 22 | -------------------------------------------------------------------------------- /spdxexp/test_helper.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | // getLicenseNode is a test helper method that is expected to create a valid 4 | // license node. Use this function when the test data is known to be a valid 5 | // license that would parse successfully. 6 | func getLicenseNode(license string, hasPlus bool) *node { 7 | return &node{ 8 | role: licenseNode, 9 | exp: nil, 10 | lic: &licenseNodePartial{ 11 | license: license, 12 | hasPlus: hasPlus, 13 | hasException: false, 14 | exception: "", 15 | }, 16 | ref: nil, 17 | } 18 | } 19 | 20 | // getParsedNode is a test helper method that is expected to create a valid node 21 | // and swallow errors. This allows test structures to use parsed node data. 22 | // Use this function when the test data is expected to parse successfully. 23 | func getParsedNode(expression string) *node { 24 | // swallows errors 25 | n, _ := parse(expression) 26 | return n 27 | } 28 | -------------------------------------------------------------------------------- /cmd/exception_ids.json: -------------------------------------------------------------------------------- 1 | ["389-exception","Autoconf-exception-2.0","Autoconf-exception-3.0","Bison-exception-2.2","Bootloader-exception","Classpath-exception-2.0","CLISP-exception-2.0","DigiRule-FOSS-exception","eCos-exception-2.0","Fawkes-Runtime-exception","FLTK-exception","Font-exception-2.0","freertos-exception-2.0","GCC-exception-2.0","GCC-exception-3.1","gnu-javamail-exception","GPL-3.0-linking-exception","GPL-3.0-linking-source-exception","GPL-CC-1.0","GStreamer-exception-2005","GStreamer-exception-2008","i2p-gpl-java-exception","KiCad-libraries-exception","LGPL-3.0-linking-exception","Libtool-exception","Linux-syscall-note","LLVM-exception","LZMA-exception","mif-exception","OCaml-LGPL-linking-exception","OCCT-exception-1.0","OpenJDK-assembly-exception-1.0","openvpn-openssl-exception","PS-or-PDF-font-exception-20170817","Qt-GPL-exception-1.0","Qt-LGPL-exception-1.1","Qwt-exception-1.0","SHL-2.0","SHL-2.1","Swift-exception","u-boot-exception-2.0","Universal-FOSS-exception-1.0","WxWindows-exception-3.1","x11vnc-openssl-exception"] -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub [issues](https://github.com/github/go-spdx/issues) to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. If not found, file your bug or feaure request as a new issue. 6 | 7 | For help or questions about using this project, please see the project [Discussions](https://github.com/github/go-spdx/discussions) where you can ask a question in [Q&A](https://github.com/github/go-spdx/discussions/categories/q-a), propose an idea in [Ideas](https://github.com/github/go-spdx/discussions/categories/ideas), or share your work in [Show and Tell](https://github.com/github/go-spdx/discussions/categories/show-and-tell). 8 | 9 | The go-spdx package is under active development and maintained by GitHub staff. Community [contributions](./CONTRIBUTING.md) are always welcome. We will do our best to respond to issues, feature requests, and community questions in a timely manner. 10 | 11 | Support for this project is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /spdxexp/spdxlicenses/get_deprecated.go: -------------------------------------------------------------------------------- 1 | package spdxlicenses 2 | 3 | // Code generated by go-spdx cmd/license.go. DO NOT EDIT. 4 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 5 | 6 | // GetDeprecated returns a slice of deprecated license IDs. 7 | func GetDeprecated() []string { 8 | return []string{ 9 | "AGPL-1.0", 10 | "AGPL-3.0", 11 | "BSD-2-Clause-FreeBSD", 12 | "BSD-2-Clause-NetBSD", 13 | "bzip2-1.0.5", 14 | "eCos-2.0", 15 | "GFDL-1.1", 16 | "GFDL-1.2", 17 | "GFDL-1.3", 18 | "GPL-1.0", 19 | "GPL-1.0+", 20 | "GPL-2.0", 21 | "GPL-2.0+", 22 | "GPL-2.0-with-autoconf-exception", 23 | "GPL-2.0-with-bison-exception", 24 | "GPL-2.0-with-classpath-exception", 25 | "GPL-2.0-with-font-exception", 26 | "GPL-2.0-with-GCC-exception", 27 | "GPL-3.0", 28 | "GPL-3.0+", 29 | "GPL-3.0-with-autoconf-exception", 30 | "GPL-3.0-with-GCC-exception", 31 | "LGPL-2.0", 32 | "LGPL-2.0+", 33 | "LGPL-2.1", 34 | "LGPL-2.1+", 35 | "LGPL-3.0", 36 | "LGPL-3.0+", 37 | "Net-SNMP", 38 | "Nunit", 39 | "StandardML-NJ", 40 | "wxWindows", 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spdxexp/extracts_test.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestExtractLicenses(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | inputExpression string 13 | extractedLicenses []string 14 | }{ 15 | {"Single license", "MIT", []string{"MIT"}}, 16 | {"AND'ed licenses", "MIT AND Apache-2.0", []string{"MIT", "Apache-2.0"}}, 17 | {"AND'ed & OR'ed licenses", "(MIT AND Apache-2.0) OR GPL-3.0", []string{"GPL-3.0", "MIT", "Apache-2.0"}}, 18 | {"ONLY modifiers", "LGPL-2.1-only OR MIT OR BSD-3-Clause", []string{"MIT", "BSD-3-Clause", "LGPL-2.1-only"}}, 19 | {"WITH modifiers", "GPL-2.0-or-later WITH Bison-exception-2.2", []string{"GPL-2.0-or-later+ WITH Bison-exception-2.2"}}, 20 | {"Invalid SPDX expression", "MIT OR INVALID", nil}, 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | licenses, err := ExtractLicenses(test.inputExpression) 26 | assert.ElementsMatch(t, test.extractedLicenses, licenses) 27 | if test.extractedLicenses == nil { 28 | assert.Error(t, err) 29 | } else { 30 | assert.NoError(t, err) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # prerequisite: 2 | # [install golangci-lint](https://golangci-lint.run/usage/install/#local-installation) 3 | run: 4 | # timeout for analysis, e.g. 30s, 5m, default is 1m 5 | timeout: 5m 6 | 7 | skip-files: 8 | - .peg\.go 9 | - .*\.pb\.go 10 | skip-dirs: 11 | - vendor 12 | 13 | linters: 14 | enable: 15 | - deadcode 16 | - depguard 17 | - errcheck 18 | - exportloopref 19 | - gocritic 20 | - gocyclo 21 | - gofmt 22 | - goimports 23 | - gosec 24 | - gosimple 25 | - govet 26 | - ineffassign 27 | - misspell 28 | - nakedret 29 | - prealloc 30 | - revive 31 | - staticcheck 32 | - structcheck 33 | - typecheck 34 | - unconvert 35 | - unused 36 | - varcheck 37 | disable: 38 | - gochecknoglobals # we allow global variables in packages 39 | - gochecknoinits # we allow inits in packages 40 | - goconst # we allow repeated values to go un-const'd 41 | - lll # we allow any line length 42 | - unparam # we allow function calls to name unused parameters 43 | 44 | issues: 45 | exclude-rules: 46 | # Probably some broken linter for generics? 47 | - linters: [ revive ] 48 | text: 'receiver-naming: receiver name \S+ should be consistent with previous receiver name \S+ for invalid-type' 49 | -------------------------------------------------------------------------------- /cmd/exception_ids.txt: -------------------------------------------------------------------------------- 1 | "389-exception", 2 | "Autoconf-exception-2.0", 3 | "Autoconf-exception-3.0", 4 | "Bison-exception-2.2", 5 | "Bootloader-exception", 6 | "Classpath-exception-2.0", 7 | "CLISP-exception-2.0", 8 | "DigiRule-FOSS-exception", 9 | "eCos-exception-2.0", 10 | "Fawkes-Runtime-exception", 11 | "FLTK-exception", 12 | "Font-exception-2.0", 13 | "freertos-exception-2.0", 14 | "GCC-exception-2.0", 15 | "GCC-exception-3.1", 16 | "gnu-javamail-exception", 17 | "GPL-3.0-linking-exception", 18 | "GPL-3.0-linking-source-exception", 19 | "GPL-CC-1.0", 20 | "GStreamer-exception-2005", 21 | "GStreamer-exception-2008", 22 | "i2p-gpl-java-exception", 23 | "KiCad-libraries-exception", 24 | "LGPL-3.0-linking-exception", 25 | "Libtool-exception", 26 | "Linux-syscall-note", 27 | "LLVM-exception", 28 | "LZMA-exception", 29 | "mif-exception", 30 | "OCaml-LGPL-linking-exception", 31 | "OCCT-exception-1.0", 32 | "OpenJDK-assembly-exception-1.0", 33 | "openvpn-openssl-exception", 34 | "PS-or-PDF-font-exception-20170817", 35 | "Qt-GPL-exception-1.0", 36 | "Qt-LGPL-exception-1.1", 37 | "Qwt-exception-1.0", 38 | "SHL-2.0", 39 | "SHL-2.1", 40 | "Swift-exception", 41 | "u-boot-exception-2.0", 42 | "Universal-FOSS-exception-1.0", 43 | "WxWindows-exception-3.1", 44 | "x11vnc-openssl-exception", 45 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | ## Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | - The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | - Full paths of source file(s) related to the manifestation of the issue 21 | - The location of the affected source code (tag/branch/commit or direct URL) 22 | - Any special configuration required to reproduce the issue 23 | - Step-by-step instructions to reproduce the issue 24 | - Proof-of-concept or exploit code (if possible) 25 | - Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/github/site-policy/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /spdxexp/license.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/github/go-spdx/v2/spdxexp/spdxlicenses" 7 | ) 8 | 9 | // activeLicense returns true if the id is an active license. 10 | func activeLicense(id string) (bool, string) { 11 | return inLicenseList(spdxlicenses.GetLicenses(), id) 12 | } 13 | 14 | // deprecatedLicense returns true if the id is a deprecated license. 15 | func deprecatedLicense(id string) (bool, string) { 16 | return inLicenseList(spdxlicenses.GetDeprecated(), id) 17 | } 18 | 19 | // exceptionLicense returns true if the id is an exception license. 20 | func exceptionLicense(id string) (bool, string) { 21 | return inLicenseList(spdxlicenses.GetExceptions(), id) 22 | } 23 | 24 | // inLicenseList looks for id in the list of licenses. The check is case-insensitive (e.g. "mit" will match "MIT"). 25 | func inLicenseList(licenses []string, id string) (bool, string) { 26 | for _, license := range licenses { 27 | if strings.EqualFold(license, id) { 28 | return true, license 29 | } 30 | } 31 | return false, id 32 | } 33 | 34 | const ( 35 | licenseGroup uint8 = iota 36 | versionGroup 37 | licenseIndex 38 | ) 39 | 40 | type licenseRange struct { 41 | licenses []string 42 | location map[uint8]int // licenseGroup, versionGroup, licenseIndex 43 | } 44 | 45 | // getLicenseRange returns a range of licenses from licenseRanges 46 | func getLicenseRange(id string) *licenseRange { 47 | simpleID := simplifyLicense(id) 48 | allRanges := spdxlicenses.LicenseRanges() 49 | for i, licenseGrp := range allRanges { 50 | for j, versionGrp := range licenseGrp { 51 | for k, license := range versionGrp { 52 | if simpleID == license { 53 | location := map[uint8]int{ 54 | licenseGroup: i, 55 | versionGroup: j, 56 | licenseIndex: k, 57 | } 58 | return &licenseRange{ 59 | licenses: versionGrp, 60 | location: location, 61 | } 62 | } 63 | } 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func simplifyLicense(id string) string { 70 | if strings.HasSuffix(id, "-or-later") { 71 | return id[0 : len(id)-9] 72 | } 73 | return id 74 | } 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/github/go-spdx 4 | [pr]: https://github.com/github/go-spdx/compare 5 | [style]: https://github.com/github/go-spdx/blob/main/.golangci.yaml 6 | [code-of-conduct]: CODE_OF_CONDUCT.md 7 | 8 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 9 | 10 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [MIT License](LICENSE.md). 11 | 12 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 13 | 14 | ## Prerequisites for running and testing code 15 | 16 | These are one time installations required to be able to test your changes locally as part of the pull request (PR) submission process. 17 | 18 | 1. install Go [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go) 19 | 1. [install golangci-lint](https://golangci-lint.run/usage/install/#local-installation) 20 | 21 | ## Submitting a pull request 22 | 23 | 1. [Fork][fork] and clone the repository 24 | 1. Configure and install the dependencies: `script/bootstrap` 25 | 1. Make sure the tests pass on your machine: `go test -v ./...` 26 | 1. Make sure linter passes on your machine: `golangci-lint run` 27 | 1. Create a new branch: `git checkout -b my-branch-name` 28 | 1. Make your change, add tests, and make sure the tests and linter still pass 29 | 1. Push to your fork and [submit a pull request][pr] 30 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 31 | 32 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 33 | 34 | - Follow the [style guide][style]. 35 | - Write tests. 36 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 37 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 38 | 39 | ## Resources 40 | 41 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 42 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 43 | - [GitHub Help](https://help.github.com) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 12 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 13 | github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= 14 | github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 15 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 16 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 17 | golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f h1:jDEaVlf+r7N8Re8Es5pGylGkfnqcx9dfUCsd1T+biTs= 18 | golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f/go.mod h1:n+8pplxVZfXnmHBxWsfPnQRJ5vWroQDk+U2MFpjwtFY= 19 | golang.org/x/tools/cmd/godoc v0.1.0-deprecated h1:sEGTwp9aZNTHsdf/2BGaRqE4ZLndRVH17rbQ2OVun9Q= 20 | golang.org/x/tools/cmd/godoc v0.1.0-deprecated/go.mod h1:J6VY4iFch6TIm456U3fnw1EJZaIqcYlhHu6GpHQ9HJk= 21 | golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= 22 | golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= 23 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 26 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 27 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go projects should have a Makefile for commond build-related tasks. 2 | # See help target for a list of targets and their descriptions. 3 | # 4 | # Debug related targets are commented out until they can be tested. 5 | 6 | # setup defaults 7 | SHELL := $(shell which bash) 8 | CWD_DIR := $(shell pwd) 9 | GITHUB_API_URL ?= https://api.github.com 10 | # DLV_BIN := $(shell go env GOPATH)/bin/dlv 11 | LINT_FILES := ./... 12 | 13 | # provide extra information when format fails 14 | define goformat 15 | files="$$(go fmt ./...)"; \ 16 | if [ -n "$${files}" ]; then \ 17 | echo "❌ ERROR: go files are not properly formatted:"; \ 18 | echo "$$files"; \ 19 | echo ""; \ 20 | echo "run the 'go fmt ./..' command or configure your editor"; \ 21 | exit 1; \ 22 | fi; 23 | endef 24 | 25 | # # install dlv if it is not already installed 26 | # define dlv 27 | # cat /proc/sys/kernel/yama/ptrace_scope | grep 0 || \ 28 | # echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope; \ 29 | # echo "Checking if '$(DLV_BIN)' exist"; \ 30 | # test -f "$(DLV_BIN)" || \ 31 | # echo "Installing dlv..." && \ 32 | # go install github.com/go-delve/delve/cmd/dlv@latest && \ 33 | # echo "Installed dlv"; 34 | # endef 35 | 36 | # NOTE: Targets defined with .PHONY are not files, they execute commands. 37 | 38 | # clean up the go modules files 39 | .PHONY: tidy 40 | tidy: 41 | @echo "==> starting tidy" 42 | go mod tidy 43 | 44 | # go format this project 45 | .PHONY: format 46 | format: 47 | @echo "==> starting format" 48 | @$(call goformat) 49 | 50 | # run some go test 51 | .PHONY: test 52 | test: 53 | @echo "==> starting test" 54 | go test ./... 55 | 56 | # runs linter for all files 57 | .PHONY: lint-all 58 | lint-all: 59 | @echo "==> starting lint for directory: ${LINT_FILES}" 60 | golangci-lint run ${LINT_FILES} 61 | 62 | # runs linter for only files with diffs from origin/main (useful for PRs) 63 | .PHONY: lint 64 | lint: 65 | @echo "==> starting lint for changed files" 66 | golangci-lint run --whole-files --new-from-rev=origin/main 67 | 68 | .PHONY: help 69 | help: 70 | @echo "Usage: make " 71 | @echo "" 72 | @echo "Targets:" 73 | @echo " tidy - clean up the go modules files" 74 | @echo " format - go format this project" 75 | @echo " test - run some go test" 76 | @echo " lint-all - runs linter for all files (optional pass in LINT_FILES=path_to_dir_or_file_to_check)" 77 | @echo " lint - runs linter for only files with diffs from origin/main (useful for PRs)" 78 | @echo " help - this help message" 79 | -------------------------------------------------------------------------------- /cmd/exceptions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type ExceptionData struct { 10 | Version string `json:"licenseListVersion"` 11 | Exceptions []Exception `json:"exceptions"` 12 | } 13 | 14 | type Exception struct { 15 | Reference string `json:"reference"` 16 | IsDeprecated bool `json:"isDeprecatedLicenseId"` 17 | DetailsURL string `json:"detailsUrl"` 18 | ReferenceNumber int `json:"referenceNumber"` 19 | Name string `json:"name"` 20 | LicenseID string `json:"licenseExceptionId"` 21 | SeeAlso []string `json:"seeAlso"` 22 | IsOsiApproved bool `json:"isOsiApproved"` 23 | } 24 | 25 | // extractExceptionLicenseIDs read official exception licenses file copied from spdx/license-list-data 26 | // and write file exception_license_ids.json containing just the non-deprecated exception license IDs. 27 | // NOTE: For now, this function ignores the deprecated exception licenses. 28 | func extractExceptionLicenseIDs() error { 29 | // open file 30 | file, err := os.Open("exceptions.json") 31 | if err != nil { 32 | fmt.Println(err) 33 | return err 34 | } 35 | 36 | // read in all licenses marshalled into a slice of exception structs 37 | var exceptionData ExceptionData 38 | err = json.NewDecoder(file).Decode(&exceptionData) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // create slice of exception license IDs that are not deprecated 44 | var exceptionLicenseIDs []string 45 | for _, e := range exceptionData.Exceptions { 46 | if !e.IsDeprecated { 47 | exceptionLicenseIDs = append(exceptionLicenseIDs, e.LicenseID) 48 | } 49 | } 50 | 51 | // generate the GetExceptions() function in get_exceptions.go 52 | getExceptionsContents := []byte(`package spdxlicenses 53 | 54 | // Code generated by go-spdx cmd/exceptions.go. DO NOT EDIT. 55 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 56 | 57 | // GetExceptions returns a slice of exception license IDs. 58 | func GetExceptions() []string { 59 | return []string{ 60 | `) 61 | for _, id := range exceptionLicenseIDs { 62 | getExceptionsContents = append(getExceptionsContents, ` "`+id+`", 63 | `...) 64 | } 65 | getExceptionsContents = append(getExceptionsContents, ` } 66 | } 67 | `...) 68 | 69 | err = os.WriteFile("../spdxexp/spdxlicenses/get_exceptions.go", getExceptionsContents, 0600) 70 | if err != nil { 71 | return err 72 | } 73 | fmt.Println("Writing `../spdxexp/spdxlicenses/get_exceptions.go`... COMPLETE") 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | cmd := "extract" 11 | 12 | if len(os.Args) > 1 { 13 | cmd = os.Args[1] 14 | } 15 | argsRemainder := []string{} 16 | if len(os.Args) > 2 { 17 | argsRemainder = os.Args[2:] 18 | } 19 | 20 | flagSet := flag.NewFlagSet("run", flag.ExitOnError) 21 | extractLicenses := flagSet.Bool("l", false, "Should license ids be extracted?") 22 | extractExceptions := flagSet.Bool("e", false, "Should exception ids be extracted?") 23 | help := flagSet.Bool("h", false, "Show help") 24 | 25 | err := flagSet.Parse(argsRemainder) 26 | if err != nil { 27 | fmt.Printf("error parsing flags for run: %v\n", err) 28 | os.Exit(1) 29 | } 30 | 31 | switch cmd { 32 | case "extract": 33 | if *help || (!*extractLicenses && !*extractExceptions) { 34 | writeHelpMessage() 35 | os.Exit(0) 36 | } 37 | if *extractLicenses { 38 | fmt.Println("-------------------------") 39 | fmt.Println("Extracting license ids...") 40 | err := extractLicenseIDs() 41 | if err != nil { 42 | fmt.Printf("error extracting license ids: %v\n", err) 43 | os.Exit(1) 44 | } 45 | fmt.Println("Done!") 46 | } 47 | if *extractExceptions { 48 | fmt.Println("---------------------------") 49 | fmt.Println("Extracting exception ids...") 50 | err := extractExceptionLicenseIDs() 51 | if err != nil { 52 | fmt.Printf("error extracting exception ids: %v\n", err) 53 | os.Exit(1) 54 | } 55 | fmt.Println("Done!") 56 | fmt.Println("---------------------------") 57 | } 58 | default: 59 | writeHelpMessage() 60 | os.Exit(0) 61 | } 62 | } 63 | 64 | func writeHelpMessage() { 65 | fmt.Println("") 66 | fmt.Println("Extracts license, deprecation, and exception ids from the official spdx license list data.") 67 | fmt.Println("The source data needs to be manually updated by copying the licenses.json file from") 68 | fmt.Println("https://github.com/spdx/license-list-data/blob/main/json/licenses.json and exceptions.json") 69 | fmt.Println("file from https://github.com/spdx/license-list-data/blob/main/json/exceptions.json.") 70 | fmt.Println("") 71 | fmt.Println("After running the extract command, the license_ids.json, deprecated_ids.json, and exception_ids.json") 72 | fmt.Println("files will be overwritten with the extracted ids. These license ids can then be used to update the") 73 | fmt.Println("spdxexp/license.go file.") 74 | fmt.Println("") 75 | fmt.Println("Command to run all extractions (run command from the /cmd directory):") 76 | fmt.Println(" `go run . extract -l -e`") 77 | fmt.Println("") 78 | fmt.Println("Usage options:") 79 | fmt.Println(" -h: prints this help message") 80 | fmt.Println(" -l: Extract license ids") 81 | fmt.Println(" -e: Extract exception ids") 82 | fmt.Println("") 83 | os.Exit(0) 84 | } 85 | -------------------------------------------------------------------------------- /spdxexp/compare.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | // The compare methods determine if two ranges are greater than, less than or equal within the same license group. 4 | // NOTE: Ranges are organized into groups (referred to as license groups) of the same base license (e.g. GPL). 5 | // Groups have sub-groups of license versions (referred to as the range) where each member is considered 6 | // to be the same version (e.g. {GPL-2.0, GPL-2.0-only}). The sub-groups are in ascending order within 7 | // the license group, such that the first sub-group is considered to be less than the second sub-group, 8 | // and so on. (e.g. {{GPL-1.0}, {GPL-2.0, GPL-2.0-only}} implies {GPL-1.0} < {GPL-2.0, GPL-2.0-only}). 9 | 10 | // compareGT returns true if the first range is greater than the second range within the same license group; otherwise, false. 11 | func compareGT(first *node, second *node) bool { 12 | if !first.isLicense() || !second.isLicense() { 13 | return false 14 | } 15 | firstRange := getLicenseRange(*first.license()) 16 | secondRange := getLicenseRange(*second.license()) 17 | 18 | if !sameLicenseGroup(firstRange, secondRange) { 19 | return false 20 | } 21 | return firstRange.location[versionGroup] > secondRange.location[versionGroup] 22 | } 23 | 24 | // compareLT returns true if the first range is less than the second range within the same license group; otherwise, false. 25 | func compareLT(first *node, second *node) bool { 26 | if !first.isLicense() || !second.isLicense() { 27 | return false 28 | } 29 | firstRange := getLicenseRange(*first.license()) 30 | secondRange := getLicenseRange(*second.license()) 31 | 32 | if !sameLicenseGroup(firstRange, secondRange) { 33 | return false 34 | } 35 | return firstRange.location[versionGroup] < secondRange.location[versionGroup] 36 | } 37 | 38 | // compareEQ returns true if the first and second range are the same range within the same license group; otherwise, false. 39 | func compareEQ(first *node, second *node) bool { 40 | if !first.isLicense() || !second.isLicense() { 41 | return false 42 | } 43 | if first.lic.license == second.lic.license { 44 | return true 45 | } 46 | 47 | firstRange := getLicenseRange(*first.license()) 48 | secondRange := getLicenseRange(*second.license()) 49 | 50 | if !sameLicenseGroup(firstRange, secondRange) { 51 | return false 52 | } 53 | return firstRange.location[versionGroup] == secondRange.location[versionGroup] 54 | } 55 | 56 | // sameLicenseGroup returns false if either license isn't in a range or the two ranges are 57 | // not in the same license group (e.g. group GPL != group Apache); otherwise, true 58 | func sameLicenseGroup(firstRange *licenseRange, secondRange *licenseRange) bool { 59 | if firstRange == nil || secondRange == nil || firstRange.location[licenseGroup] != secondRange.location[licenseGroup] { 60 | return false 61 | } 62 | return true 63 | } 64 | -------------------------------------------------------------------------------- /spdxexp/spdxlicenses/get_exceptions.go: -------------------------------------------------------------------------------- 1 | package spdxlicenses 2 | 3 | // Code generated by go-spdx cmd/exceptions.go. DO NOT EDIT. 4 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 5 | 6 | // GetExceptions returns a slice of exception license IDs. 7 | func GetExceptions() []string { 8 | return []string{ 9 | "389-exception", 10 | "Asterisk-exception", 11 | "Asterisk-linking-protocols-exception", 12 | "Autoconf-exception-2.0", 13 | "Autoconf-exception-3.0", 14 | "Autoconf-exception-generic", 15 | "Autoconf-exception-generic-3.0", 16 | "Autoconf-exception-macro", 17 | "Bison-exception-1.24", 18 | "Bison-exception-2.2", 19 | "Bootloader-exception", 20 | "CGAL-linking-exception", 21 | "Classpath-exception-2.0", 22 | "Classpath-exception-2.0-short", 23 | "CLISP-exception-2.0", 24 | "cryptsetup-OpenSSL-exception", 25 | "Digia-Qt-LGPL-exception-1.1", 26 | "DigiRule-FOSS-exception", 27 | "eCos-exception-2.0", 28 | "erlang-otp-linking-exception", 29 | "Fawkes-Runtime-exception", 30 | "FLTK-exception", 31 | "fmt-exception", 32 | "Font-exception-2.0", 33 | "freertos-exception-2.0", 34 | "GCC-exception-2.0", 35 | "GCC-exception-2.0-note", 36 | "GCC-exception-3.1", 37 | "Gmsh-exception", 38 | "GNAT-exception", 39 | "GNOME-examples-exception", 40 | "GNU-compiler-exception", 41 | "gnu-javamail-exception", 42 | "GPL-3.0-389-ds-base-exception", 43 | "GPL-3.0-interface-exception", 44 | "GPL-3.0-linking-exception", 45 | "GPL-3.0-linking-source-exception", 46 | "GPL-CC-1.0", 47 | "GStreamer-exception-2005", 48 | "GStreamer-exception-2008", 49 | "harbour-exception", 50 | "i2p-gpl-java-exception", 51 | "Independent-modules-exception", 52 | "KiCad-libraries-exception", 53 | "kvirc-openssl-exception", 54 | "LGPL-3.0-linking-exception", 55 | "libpri-OpenH323-exception", 56 | "Libtool-exception", 57 | "Linux-syscall-note", 58 | "LLGPL", 59 | "LLVM-exception", 60 | "LZMA-exception", 61 | "mif-exception", 62 | "mxml-exception", 63 | "OCaml-LGPL-linking-exception", 64 | "OCCT-exception-1.0", 65 | "OpenJDK-assembly-exception-1.0", 66 | "openvpn-openssl-exception", 67 | "PCRE2-exception", 68 | "polyparse-exception", 69 | "PS-or-PDF-font-exception-20170817", 70 | "QPL-1.0-INRIA-2004-exception", 71 | "Qt-GPL-exception-1.0", 72 | "Qt-LGPL-exception-1.1", 73 | "Qwt-exception-1.0", 74 | "romic-exception", 75 | "RRDtool-FLOSS-exception-2.0", 76 | "SANE-exception", 77 | "SHL-2.0", 78 | "SHL-2.1", 79 | "Simple-Library-Usage-exception", 80 | "stunnel-exception", 81 | "SWI-exception", 82 | "Swift-exception", 83 | "Texinfo-exception", 84 | "u-boot-exception-2.0", 85 | "UBDL-exception", 86 | "Universal-FOSS-exception-1.0", 87 | "vsftpd-openssl-exception", 88 | "WxWindows-exception-3.1", 89 | "x11vnc-openssl-exception", 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /cmd/license.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type LicenseData struct { 10 | Version string `json:"licenseListVersion"` 11 | Licenses []License `json:"licenses"` 12 | } 13 | 14 | type License struct { 15 | Reference string `json:"reference"` 16 | IsDeprecated bool `json:"isDeprecatedLicenseId"` 17 | DetailsURL string `json:"detailsUrl"` 18 | ReferenceNumber int `json:"referenceNumber"` 19 | Name string `json:"name"` 20 | LicenseID string `json:"licenseId"` 21 | SeeAlso []string `json:"seeAlso"` 22 | IsOsiApproved bool `json:"isOsiApproved"` 23 | } 24 | 25 | // extractLicenseIDs reads the official licenses.json file copied from spdx/license-list-data 26 | // and writes two files, license_ids.json and deprecated_license_ids.json, containing just 27 | // the license IDs and deprecated license IDs, respectively. It returns an error if it 28 | // encounters one. 29 | func extractLicenseIDs() error { 30 | // open file 31 | file, err := os.Open("licenses.json") 32 | if err != nil { 33 | fmt.Println(err) 34 | return err 35 | } 36 | 37 | // read in all licenses marshalled into a slice of license structs 38 | var licenseData LicenseData 39 | err = json.NewDecoder(file).Decode(&licenseData) 40 | if err != nil { 41 | fmt.Println(err) 42 | return err 43 | } 44 | 45 | // create two slices of license IDs, one for deprecated and one for active 46 | var activeLicenseIDs []string 47 | var deprecatedLicenseIDs []string 48 | for _, l := range licenseData.Licenses { 49 | if l.IsDeprecated { 50 | deprecatedLicenseIDs = append(deprecatedLicenseIDs, l.LicenseID) 51 | } else { 52 | activeLicenseIDs = append(activeLicenseIDs, l.LicenseID) 53 | } 54 | } 55 | 56 | // generate the GetLicenses() function in get_licenses.go 57 | getLicensesContents := []byte(`package spdxlicenses 58 | 59 | // Code generated by go-spdx cmd/license.go. DO NOT EDIT. 60 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 61 | 62 | // GetLicenses returns a slice of active license IDs. 63 | func GetLicenses() []string { 64 | return []string{ 65 | `) 66 | for _, id := range activeLicenseIDs { 67 | getLicensesContents = append(getLicensesContents, ` "`+id+`", 68 | `...) 69 | } 70 | getLicensesContents = append(getLicensesContents, ` } 71 | } 72 | `...) 73 | 74 | err = os.WriteFile("../spdxexp/spdxlicenses/get_licenses.go", getLicensesContents, 0600) 75 | if err != nil { 76 | return err 77 | } 78 | fmt.Println("Writing `../spdxexp/spdxlicenses/get_licenses.go`... COMPLETE") 79 | 80 | // generate the GetDeprecated() function in get_deprecated.go 81 | getDeprecatedContents := []byte(`package spdxlicenses 82 | 83 | // Code generated by go-spdx cmd/license.go. DO NOT EDIT. 84 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 85 | 86 | // GetDeprecated returns a slice of deprecated license IDs. 87 | func GetDeprecated() []string { 88 | return []string{ 89 | `) 90 | for _, id := range deprecatedLicenseIDs { 91 | getDeprecatedContents = append(getDeprecatedContents, ` "`+id+`", 92 | `...) 93 | } 94 | getDeprecatedContents = append(getDeprecatedContents, ` } 95 | } 96 | `...) 97 | 98 | err = os.WriteFile("../spdxexp/spdxlicenses/get_deprecated.go", getDeprecatedContents, 0600) 99 | if err != nil { 100 | return err 101 | } 102 | fmt.Println("Writing `../spdxexp/spdxlicenses/get_deprecated.go`... COMPLETE") 103 | 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/fetch-licenses.yaml: -------------------------------------------------------------------------------- 1 | name: Fetch Licenses 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | force_run: 7 | description: 'Force run license extraction' 8 | required: false 9 | default: 'false' 10 | schedule: 11 | - cron: '5 4 * * *' # Runs at 0405 UTC (0605 CET, 0005 ET, 2105 PT) 12 | 13 | jobs: 14 | fetch-licenses: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write 18 | pull-requests: write 19 | 20 | steps: 21 | - name: Setup Git 22 | run: | 23 | git config --global user.name "GitHub Actions" 24 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 25 | 26 | - name: Checkout this repository 27 | uses: actions/checkout@v6 28 | 29 | - name: Checkout official SPDX Repository 30 | uses: actions/checkout@v6 31 | with: 32 | repository: spdx/license-list-data 33 | path: official-spdx-licenses # creates a tmp dir to hold the SPDX licenses from the official repo 34 | 35 | - name: Copy Licenses 36 | run: | 37 | cp official-spdx-licenses/json/licenses.json cmd/licenses.json 38 | cp official-spdx-licenses/json/exceptions.json cmd/exceptions.json 39 | 40 | - name: Get date 41 | id: date 42 | run: echo "DT_STAMP"=$(date +'%Y-%m-%d %H:%M UTC') >> $GITHUB_ENV 43 | 44 | - name: Check for changes in SPDX json files 45 | id: src-changes 46 | run: | 47 | src_changed=true 48 | # --quiet: Exits with 1 if there are changes; otherwise, exits with 0 49 | git diff --quiet -- cmd/licenses.json cmd/exceptions.json && src_changed=false 50 | if [ $src_changed != 'true' ]; then 51 | if [ ${{ github.event.inputs.force_run }} == 'true' ]; then 52 | echo -e '***************\nNo changes in spdx json, but skipping abort due to force run\n***************' 53 | else 54 | echo -e '***************\nABORTING: No changes to spdx json data\n***************' 55 | exit 0 56 | fi 57 | fi 58 | echo "SRC_CHANGED=$src_changed" >> $GITHUB_ENV 59 | 60 | - name: Run license extraction 61 | if: ${{ env.SRC_CHANGED == 'true' || github.event.inputs.force_run == 'true' }} 62 | run: | 63 | cd cmd 64 | echo "Current branch: $(git branch)" 65 | go run . extract -l -e 66 | cd .. 67 | git log --oneline -n 5 68 | 69 | - name: Check for changes in generated files 70 | id: genfiles-changes 71 | run: | 72 | genfiles_changed=true 73 | # --quiet: Exits with 1 if there are changes; otherwise, exits with 0 74 | git diff --quiet -- spdxexp/spdxlicenses/*.go && genfiles_changed=false 75 | if [ $genfiles_changed != 'true' ]; then 76 | if [ ${{ github.event.inputs.force_run }} == 'true' ]; then 77 | echo -e '***************\nNo changes to generated files, but skipping abort due to force run\n***************' 78 | else 79 | # This happens when the generated files are already up to date 80 | # or the only changes in the source json are data not included in the generated files (e.g. IDs) 81 | echo -e '***************\nABORTING: No changes to license data in generated files\n***************' 82 | exit 0 83 | fi 84 | fi 85 | echo "GENFILES_CHANGED=$genfiles_changed" >> $GITHUB_ENV 86 | 87 | - name: Create Pull Request 88 | if: ${{ env.GENFILES_CHANGED == 'true' || github.event.inputs.force_run == 'true' }} 89 | uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 90 | with: 91 | token: ${{ secrets.GITHUB_TOKEN }} 92 | commit-message: Add updated license files 93 | branch: auto-update-licenses 94 | base: main 95 | title: "Update SPDX license files (${{ env.DT_STAMP }})" 96 | body: "The files in this PR are auto-generated by the [fetch-licenses](./.git/workflows/fetch-license.yaml) workflow when it runs the `extract` command defined in [cmd/main.go](./cmd/main.go). It updates SPDX licenses based on the latest released set in the [spdx/license-list-data](https://github.com/spdx/license-list-data) repository maintained by [SPDX](https://spdx.org/licenses/). \n\nTODO: [spdxexp/spdxlicenses/license_ranges.go](./spdxexp/spdxlicenses/license_range.go) has to be updated manually." 97 | labels: 'auto-update,licenses' 98 | -------------------------------------------------------------------------------- /spdxexp/license_test.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestActiveLicense(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | inputID string 13 | outputID string 14 | result bool 15 | }{ 16 | {"active license - direct match", "Apache-2.0", "Apache-2.0", true}, 17 | {"active license - all upper", "APACHE-2.0", "Apache-2.0", true}, 18 | {"active license - all lower", "apache-2.0", "Apache-2.0", true}, 19 | {"active license - mixed case", "apACHe-2.0", "Apache-2.0", true}, 20 | {"deprecated license - direct match", "eCos-2.0", "eCos-2.0", false}, 21 | {"deprecated license - all upper", "ECOS-2.0", "eCos-2.0", false}, 22 | {"deprecated license - all lower", "ecos-2.0", "eCos-2.0", false}, 23 | {"deprecated license - mixed case", "ECos-2.0", "eCos-2.0", false}, 24 | {"exception license - direct match", "Bison-exception-2.2", "Bison-exception-2.2", false}, 25 | {"exception license - all upper", "BISON-EXCEPTION-2.2", "Bison-exception-2.2", false}, 26 | {"exception license - all lower", "bison-exception-2.2", "Bison-exception-2.2", false}, 27 | {"exception license - mixed case", "BisoN-Exception-2.2", "Bison-exception-2.2", false}, 28 | } 29 | 30 | for _, test := range tests { 31 | test := test 32 | t.Run(test.name, func(t *testing.T) { 33 | result, license := activeLicense((test.inputID)) 34 | assert.Equal(t, test.result, result) 35 | if result { 36 | // updated to the proper case if found 37 | assert.Equal(t, test.outputID, license) 38 | } else { 39 | // no change in case if not found 40 | assert.Equal(t, test.inputID, license) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func TestDeprecatedLicense(t *testing.T) { 47 | tests := []struct { 48 | name string 49 | inputID string 50 | outputID string 51 | result bool 52 | }{ 53 | {"active license - direct match", "Apache-2.0", "Apache-2.0", false}, 54 | {"active license - all upper", "APACHE-2.0", "Apache-2.0", false}, 55 | {"active license - all lower", "apache-2.0", "Apache-2.0", false}, 56 | {"active license - mixed case", "apACHe-2.0", "Apache-2.0", false}, 57 | {"deprecated license - direct match", "eCos-2.0", "eCos-2.0", true}, 58 | {"deprecated license - all upper", "ECOS-2.0", "eCos-2.0", true}, 59 | {"deprecated license - all lower", "ecos-2.0", "eCos-2.0", true}, 60 | {"deprecated license - mixed case", "ECos-2.0", "eCos-2.0", true}, 61 | {"exception license - direct match", "Bison-exception-2.2", "Bison-exception-2.2", false}, 62 | {"exception license - all upper", "BISON-EXCEPTION-2.2", "Bison-exception-2.2", false}, 63 | {"exception license - all lower", "bison-exception-2.2", "Bison-exception-2.2", false}, 64 | {"exception license - mixed case", "BisoN-Exception-2.2", "Bison-exception-2.2", false}, 65 | } 66 | 67 | for _, test := range tests { 68 | test := test 69 | t.Run(test.name, func(t *testing.T) { 70 | result, license := deprecatedLicense((test.inputID)) 71 | assert.Equal(t, test.result, result) 72 | if result { 73 | // updated to the proper case if found 74 | assert.Equal(t, test.outputID, license) 75 | } else { 76 | // no change in case if not found 77 | assert.Equal(t, test.inputID, license) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestExceptionLicense(t *testing.T) { 84 | tests := []struct { 85 | name string 86 | inputID string 87 | outputID string 88 | result bool 89 | }{ 90 | {"active license - direct match", "Apache-2.0", "Apache-2.0", false}, 91 | {"active license - all upper", "APACHE-2.0", "Apache-2.0", false}, 92 | {"active license - all lower", "apache-2.0", "Apache-2.0", false}, 93 | {"active license - mixed case", "apACHe-2.0", "Apache-2.0", false}, 94 | {"deprecated license - direct match", "eCos-2.0", "eCos-2.0", false}, 95 | {"deprecated license - all upper", "ECOS-2.0", "eCos-2.0", false}, 96 | {"deprecated license - all lower", "ecos-2.0", "eCos-2.0", false}, 97 | {"deprecated license - mixed case", "ECos-2.0", "eCos-2.0", false}, 98 | {"exception license - direct match", "Bison-exception-2.2", "Bison-exception-2.2", true}, 99 | {"exception license - all upper", "BISON-EXCEPTION-2.2", "Bison-exception-2.2", true}, 100 | {"exception license - all lower", "bison-exception-2.2", "Bison-exception-2.2", true}, 101 | {"exception license - mixed case", "BisoN-Exception-2.2", "Bison-exception-2.2", true}, 102 | } 103 | 104 | for _, test := range tests { 105 | test := test 106 | t.Run(test.name, func(t *testing.T) { 107 | result, license := exceptionLicense((test.inputID)) 108 | assert.Equal(t, test.result, result) 109 | if result { 110 | // updated to the proper case if found 111 | assert.Equal(t, test.outputID, license) 112 | } else { 113 | // no change in case if not found 114 | assert.Equal(t, test.inputID, license) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | func TestGetLicenseRange(t *testing.T) { 121 | tests := []struct { 122 | name string 123 | id string 124 | licenseRange *licenseRange 125 | }{ 126 | {"no multi-element ranges", "Apache-2.0", &licenseRange{ 127 | licenses: []string{"Apache-2.0"}, 128 | location: map[uint8]int{licenseGroup: 2, versionGroup: 2, licenseIndex: 0}}}, 129 | {"multi-element ranges", "GFDL-1.2-only", &licenseRange{ 130 | licenses: []string{"GFDL-1.2", "GFDL-1.2-only"}, 131 | location: map[uint8]int{licenseGroup: 21, versionGroup: 1, licenseIndex: 1}}}, 132 | {"no range", "Bison-exception-2.2", nil}, 133 | } 134 | 135 | for _, test := range tests { 136 | test := test 137 | t.Run(test.name, func(t *testing.T) { 138 | licenseRange := getLicenseRange(test.id) 139 | assert.Equal(t, test.licenseRange, licenseRange) 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /cmd/license_ids.json: -------------------------------------------------------------------------------- 1 | ["0BSD","AAL","Abstyles","Adobe-2006","Adobe-Glyph","ADSL","AFL-1.1","AFL-1.2","AFL-2.0","AFL-2.1","AFL-3.0","Afmparse","AGPL-1.0-only","AGPL-1.0-or-later","AGPL-3.0-only","AGPL-3.0-or-later","Aladdin","AMDPLPA","AML","AMPAS","ANTLR-PD","ANTLR-PD-fallback","Apache-1.0","Apache-1.1","Apache-2.0","APAFML","APL-1.0","App-s2p","APSL-1.0","APSL-1.1","APSL-1.2","APSL-2.0","Arphic-1999","Artistic-1.0","Artistic-1.0-cl8","Artistic-1.0-Perl","Artistic-2.0","Baekmuk","Bahyph","Barr","Beerware","Bitstream-Charter","Bitstream-Vera","BitTorrent-1.0","BitTorrent-1.1","blessing","BlueOak-1.0.0","Borceux","BSD-1-Clause","BSD-2-Clause","BSD-2-Clause-Patent","BSD-2-Clause-Views","BSD-3-Clause","BSD-3-Clause-Attribution","BSD-3-Clause-Clear","BSD-3-Clause-LBNL","BSD-3-Clause-Modification","BSD-3-Clause-No-Military-License","BSD-3-Clause-No-Nuclear-License","BSD-3-Clause-No-Nuclear-License-2014","BSD-3-Clause-No-Nuclear-Warranty","BSD-3-Clause-Open-MPI","BSD-4-Clause","BSD-4-Clause-Shortened","BSD-4-Clause-UC","BSD-Protection","BSD-Source-Code","BSL-1.0","BUSL-1.1","bzip2-1.0.6","C-UDA-1.0","CAL-1.0","CAL-1.0-Combined-Work-Exception","Caldera","CATOSL-1.1","CC-BY-1.0","CC-BY-2.0","CC-BY-2.5","CC-BY-2.5-AU","CC-BY-3.0","CC-BY-3.0-AT","CC-BY-3.0-DE","CC-BY-3.0-IGO","CC-BY-3.0-NL","CC-BY-3.0-US","CC-BY-4.0","CC-BY-NC-1.0","CC-BY-NC-2.0","CC-BY-NC-2.5","CC-BY-NC-3.0","CC-BY-NC-3.0-DE","CC-BY-NC-4.0","CC-BY-NC-ND-1.0","CC-BY-NC-ND-2.0","CC-BY-NC-ND-2.5","CC-BY-NC-ND-3.0","CC-BY-NC-ND-3.0-DE","CC-BY-NC-ND-3.0-IGO","CC-BY-NC-ND-4.0","CC-BY-NC-SA-1.0","CC-BY-NC-SA-2.0","CC-BY-NC-SA-2.0-DE","CC-BY-NC-SA-2.0-FR","CC-BY-NC-SA-2.0-UK","CC-BY-NC-SA-2.5","CC-BY-NC-SA-3.0","CC-BY-NC-SA-3.0-DE","CC-BY-NC-SA-3.0-IGO","CC-BY-NC-SA-4.0","CC-BY-ND-1.0","CC-BY-ND-2.0","CC-BY-ND-2.5","CC-BY-ND-3.0","CC-BY-ND-3.0-DE","CC-BY-ND-4.0","CC-BY-SA-1.0","CC-BY-SA-2.0","CC-BY-SA-2.0-UK","CC-BY-SA-2.1-JP","CC-BY-SA-2.5","CC-BY-SA-3.0","CC-BY-SA-3.0-AT","CC-BY-SA-3.0-DE","CC-BY-SA-4.0","CC-PDDC","CC0-1.0","CDDL-1.0","CDDL-1.1","CDL-1.0","CDLA-Permissive-1.0","CDLA-Permissive-2.0","CDLA-Sharing-1.0","CECILL-1.0","CECILL-1.1","CECILL-2.0","CECILL-2.1","CECILL-B","CECILL-C","CERN-OHL-1.1","CERN-OHL-1.2","CERN-OHL-P-2.0","CERN-OHL-S-2.0","CERN-OHL-W-2.0","checkmk","ClArtistic","CNRI-Jython","CNRI-Python","CNRI-Python-GPL-Compatible","COIL-1.0","Community-Spec-1.0","Condor-1.1","copyleft-next-0.3.0","copyleft-next-0.3.1","CPAL-1.0","CPL-1.0","CPOL-1.02","Crossword","CrystalStacker","CUA-OPL-1.0","Cube","curl","D-FSL-1.0","diffmark","DL-DE-BY-2.0","DOC","Dotseqn","DRL-1.0","DSDP","dvipdfm","ECL-1.0","ECL-2.0","EFL-1.0","EFL-2.0","eGenix","Elastic-2.0","Entessa","EPICS","EPL-1.0","EPL-2.0","ErlPL-1.1","etalab-2.0","EUDatagrid","EUPL-1.0","EUPL-1.1","EUPL-1.2","Eurosym","Fair","FDK-AAC","Frameworx-1.0","FreeBSD-DOC","FreeImage","FSFAP","FSFUL","FSFULLR","FSFULLRWD","FTL","GD","GFDL-1.1-invariants-only","GFDL-1.1-invariants-or-later","GFDL-1.1-no-invariants-only","GFDL-1.1-no-invariants-or-later","GFDL-1.1-only","GFDL-1.1-or-later","GFDL-1.2-invariants-only","GFDL-1.2-invariants-or-later","GFDL-1.2-no-invariants-only","GFDL-1.2-no-invariants-or-later","GFDL-1.2-only","GFDL-1.2-or-later","GFDL-1.3-invariants-only","GFDL-1.3-invariants-or-later","GFDL-1.3-no-invariants-only","GFDL-1.3-no-invariants-or-later","GFDL-1.3-only","GFDL-1.3-or-later","Giftware","GL2PS","Glide","Glulxe","GLWTPL","gnuplot","GPL-1.0-only","GPL-1.0-or-later","GPL-2.0-only","GPL-2.0-or-later","GPL-3.0-only","GPL-3.0-or-later","Graphics-Gems","gSOAP-1.3b","HaskellReport","Hippocratic-2.1","HPND","HPND-export-US","HPND-sell-variant","HTMLTIDY","IBM-pibs","ICU","IJG","IJG-short","ImageMagick","iMatix","Imlib2","Info-ZIP","Intel","Intel-ACPI","Interbase-1.0","IPA","IPL-1.0","ISC","Jam","JasPer-2.0","JPNIC","JSON","Knuth-CTAN","LAL-1.2","LAL-1.3","Latex2e","Leptonica","LGPL-2.0-only","LGPL-2.0-or-later","LGPL-2.1-only","LGPL-2.1-or-later","LGPL-3.0-only","LGPL-3.0-or-later","LGPLLR","Libpng","libpng-2.0","libselinux-1.0","libtiff","libutil-David-Nugent","LiLiQ-P-1.1","LiLiQ-R-1.1","LiLiQ-Rplus-1.1","Linux-man-pages-copyleft","Linux-OpenIB","LOOP","LPL-1.0","LPL-1.02","LPPL-1.0","LPPL-1.1","LPPL-1.2","LPPL-1.3a","LPPL-1.3c","LZMA-SDK-9.11-to-9.20","LZMA-SDK-9.22","MakeIndex","Minpack","MirOS","MIT","MIT-0","MIT-advertising","MIT-CMU","MIT-enna","MIT-feh","MIT-Modern-Variant","MIT-open-group","MIT-Wu","MITNFA","Motosoto","mpi-permissive","mpich2","MPL-1.0","MPL-1.1","MPL-2.0","MPL-2.0-no-copyleft-exception","mplus","MS-LPL","MS-PL","MS-RL","MTLL","MulanPSL-1.0","MulanPSL-2.0","Multics","Mup","NAIST-2003","NASA-1.3","Naumen","NBPL-1.0","NCGL-UK-2.0","NCSA","Net-SNMP","NetCDF","Newsletr","NGPL","NICTA-1.0","NIST-PD","NIST-PD-fallback","NLOD-1.0","NLOD-2.0","NLPL","Nokia","NOSL","Noweb","NPL-1.0","NPL-1.1","NPOSL-3.0","NRL","NTP","NTP-0","O-UDA-1.0","OCCT-PL","OCLC-2.0","ODbL-1.0","ODC-By-1.0","OFL-1.0","OFL-1.0-no-RFN","OFL-1.0-RFN","OFL-1.1","OFL-1.1-no-RFN","OFL-1.1-RFN","OGC-1.0","OGDL-Taiwan-1.0","OGL-Canada-2.0","OGL-UK-1.0","OGL-UK-2.0","OGL-UK-3.0","OGTSL","OLDAP-1.1","OLDAP-1.2","OLDAP-1.3","OLDAP-1.4","OLDAP-2.0","OLDAP-2.0.1","OLDAP-2.1","OLDAP-2.2","OLDAP-2.2.1","OLDAP-2.2.2","OLDAP-2.3","OLDAP-2.4","OLDAP-2.5","OLDAP-2.6","OLDAP-2.7","OLDAP-2.8","OML","OpenSSL","OPL-1.0","OPUBL-1.0","OSET-PL-2.1","OSL-1.0","OSL-1.1","OSL-2.0","OSL-2.1","OSL-3.0","Parity-6.0.0","Parity-7.0.0","PDDL-1.0","PHP-3.0","PHP-3.01","Plexus","PolyForm-Noncommercial-1.0.0","PolyForm-Small-Business-1.0.0","PostgreSQL","PSF-2.0","psfrag","psutils","Python-2.0","Python-2.0.1","Qhull","QPL-1.0","Rdisc","RHeCos-1.1","RPL-1.1","RPL-1.5","RPSL-1.0","RSA-MD","RSCPL","Ruby","SAX-PD","Saxpath","SCEA","SchemeReport","Sendmail","Sendmail-8.23","SGI-B-1.0","SGI-B-1.1","SGI-B-2.0","SHL-0.5","SHL-0.51","SimPL-2.0","SISSL","SISSL-1.2","Sleepycat","SMLNJ","SMPPL","SNIA","Spencer-86","Spencer-94","Spencer-99","SPL-1.0","SSH-OpenSSH","SSH-short","SSPL-1.0","SugarCRM-1.1.3","SWL","Symlinks","TAPR-OHL-1.0","TCL","TCP-wrappers","TMate","TORQUE-1.1","TOSL","TPDL","TTWL","TU-Berlin-1.0","TU-Berlin-2.0","UCL-1.0","Unicode-DFS-2015","Unicode-DFS-2016","Unicode-TOU","Unlicense","UPL-1.0","Vim","VOSTROM","VSL-1.0","W3C","W3C-19980720","W3C-20150513","Watcom-1.0","Wsuipa","WTFPL","X11","X11-distribute-modifications-variant","Xerox","XFree86-1.1","xinetd","Xnet","xpp","XSkat","YPL-1.0","YPL-1.1","Zed","Zend-2.0","Zimbra-1.3","Zimbra-1.4","Zlib","zlib-acknowledgement","ZPL-1.1","ZPL-2.0","ZPL-2.1"] -------------------------------------------------------------------------------- /spdxexp/compare_test.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCompareGT(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | first *node 13 | second *node 14 | result bool 15 | }{ 16 | {"expect greater than: GPL-3.0 > GPL-2.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-2.0", false), true}, 17 | {"expect greater than: GPL-3.0-only > GPL-2.0-only", getLicenseNode("GPL-3.0-only", false), getLicenseNode("GPL-2.0-only", false), true}, 18 | {"expect greater than: LPPL-1.3a > LPPL-1.0", getLicenseNode("LPPL-1.3a", false), getLicenseNode("LPPL-1.0", false), true}, 19 | {"expect greater than: LPPL-1.3c > LPPL-1.3a", getLicenseNode("LPPL-1.3c", false), getLicenseNode("LPPL-1.3a", false), true}, 20 | {"expect greater than: AGPL-3.0 > AGPL-1.0", getLicenseNode("AGPL-3.0", false), getLicenseNode("AGPL-1.0", false), true}, 21 | {"expect equal: GPL-2.0-or-later > GPL-2.0-only", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0-only", false), false}, 22 | {"expect equal: GPL-2.0-or-later > GPL-2.0", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0", false), false}, 23 | {"expect equal: GPL-2.0-only > GPL-2.0", getLicenseNode("GPL-2.0-only", false), getLicenseNode("GPL-2.0", false), false}, 24 | {"expect equal: GPL-3.0 > GPL-3.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-3.0", false), false}, 25 | {"expect equal: MIT > MIT", getLicenseNode("MIT", false), getLicenseNode("MIT", false), false}, 26 | {"expect less than: MPL-1.0 > MPL-2.0", getLicenseNode("MPL-1.0", false), getLicenseNode("MPL-2.0", false), false}, 27 | {"incompatible: MIT > ISC", getLicenseNode("MIT", false), getLicenseNode("ISC", false), false}, 28 | {"incompatible: MIT > GPL-2.0-only", getLicenseNode("MIT", false), getLicenseNode("GPL-2.0-only", false), false}, 29 | {"incompatible: OSL-1.0 > OPL-1.0", getLicenseNode("OSL-1.0", false), getLicenseNode("OPL-1.0", false), false}, 30 | {"not simple license: (MIT OR ISC) > GPL-3.0", getLicenseNode("(MIT OR ISC)", false), getLicenseNode("GPL-3.0", false), false}, // TODO: should it raise error? 31 | } 32 | 33 | for _, test := range tests { 34 | test := test 35 | t.Run(test.name, func(t *testing.T) { 36 | assert.Equal(t, test.result, compareGT(test.first, test.second)) 37 | }) 38 | } 39 | } 40 | 41 | func TestCompareEQ(t *testing.T) { 42 | tests := []struct { 43 | name string 44 | first *node 45 | second *node 46 | result bool 47 | }{ 48 | {"expect greater than: GPL-3.0 == GPL-2.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-2.0", false), false}, 49 | {"expect greater than: GPL-3.0-only == GPL-2.0-only", getLicenseNode("GPL-3.0-only", false), getLicenseNode("GPL-2.0-only", false), false}, 50 | {"expect greater than: LPPL-1.3a == LPPL-1.0", getLicenseNode("LPPL-1.3a", false), getLicenseNode("LPPL-1.0", false), false}, 51 | {"expect greater than: LPPL-1.3c == LPPL-1.3a", getLicenseNode("LPPL-1.3c", false), getLicenseNode("LPPL-1.3a", false), false}, 52 | {"expect greater than: AGPL-3.0 == AGPL-1.0", getLicenseNode("AGPL-3.0", false), getLicenseNode("AGPL-1.0", false), false}, 53 | {"expect equal: GPL-2.0-or-later > GPL-2.0-only", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0-only", false), true}, 54 | {"expect equal: GPL-2.0-or-later > GPL-2.0", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0", false), true}, 55 | {"expect equal: GPL-2.0-only == GPL-2.0", getLicenseNode("GPL-2.0-only", false), getLicenseNode("GPL-2.0", false), true}, 56 | {"expect equal: GPL-3.0 == GPL-3.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-3.0", false), true}, 57 | {"expect equal: MIT == MIT", getLicenseNode("MIT", false), getLicenseNode("MIT", false), true}, 58 | {"expect less than: MPL-1.0 == MPL-2.0", getLicenseNode("MPL-1.0", false), getLicenseNode("MPL-2.0", false), false}, 59 | {"incompatible: MIT == ISC", getLicenseNode("MIT", false), getLicenseNode("ISC", false), false}, 60 | {"incompatible: MIT == GPL-2.0-only", getLicenseNode("MIT", false), getLicenseNode("GPL-2.0-only", false), false}, 61 | {"incompatible: OSL-1.0 == OPL-1.0", getLicenseNode("OSL-1.0", false), getLicenseNode("OPL-1.0", false), false}, 62 | {"not simple license: (MIT OR ISC) == GPL-3.0", getLicenseNode("(MIT OR ISC)", false), getLicenseNode("GPL-3.0", false), false}, // TODO: should it raise error? 63 | } 64 | 65 | for _, test := range tests { 66 | test := test 67 | t.Run(test.name, func(t *testing.T) { 68 | assert.Equal(t, test.result, compareEQ(test.first, test.second)) 69 | }) 70 | } 71 | } 72 | 73 | func TestCompareLT(t *testing.T) { 74 | tests := []struct { 75 | name string 76 | first *node 77 | second *node 78 | result bool 79 | }{ 80 | {"expect greater than: GPL-3.0 < GPL-2.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-2.0", false), false}, 81 | {"expect greater than: GPL-3.0-only < GPL-2.0-only", getLicenseNode("GPL-3.0-only", false), getLicenseNode("GPL-2.0-only", false), false}, 82 | {"expect greater than: LPPL-1.3a < LPPL-1.0", getLicenseNode("LPPL-1.3a", false), getLicenseNode("LPPL-1.0", false), false}, 83 | {"expect greater than: LPPL-1.3c < LPPL-1.3a", getLicenseNode("LPPL-1.3c", false), getLicenseNode("LPPL-1.3a", false), false}, 84 | {"expect greater than: AGPL-3.0 < AGPL-1.0", getLicenseNode("AGPL-3.0", false), getLicenseNode("AGPL-1.0", false), false}, 85 | {"expect greater than: GPL-2.0-or-later < GPL-2.0-only", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0-only", false), false}, 86 | {"expect greater than: GPL-2.0-or-later == GPL-2.0", getLicenseNode("GPL-2.0-or-later", true), getLicenseNode("GPL-2.0", false), false}, 87 | {"expect equal: GPL-2.0-only < GPL-2.0", getLicenseNode("GPL-2.0-only", false), getLicenseNode("GPL-2.0", false), false}, 88 | {"expect equal: GPL-3.0 < GPL-3.0", getLicenseNode("GPL-3.0", false), getLicenseNode("GPL-3.0", false), false}, 89 | {"expect equal: MIT < MIT", getLicenseNode("MIT", false), getLicenseNode("MIT", false), false}, 90 | {"expect less than: MPL-1.0 < MPL-2.0", getLicenseNode("MPL-1.0", false), getLicenseNode("MPL-2.0", false), true}, 91 | {"incompatible: MIT < ISC", getLicenseNode("MIT", false), getLicenseNode("ISC", false), false}, 92 | {"incompatible: MIT < GPL-2.0-only", getLicenseNode("MIT", false), getLicenseNode("GPL-2.0-only", false), false}, 93 | {"incompatible: OSL-1.0 < OPL-1.0", getLicenseNode("OSL-1.0", false), getLicenseNode("OPL-1.0", false), false}, 94 | {"not simple license: (MIT OR ISC) < GPL-3.0", getLicenseNode("(MIT OR ISC)", false), getLicenseNode("GPL-3.0", false), false}, // TODO: should it raise error? 95 | } 96 | 97 | for _, test := range tests { 98 | test := test 99 | t.Run(test.name, func(t *testing.T) { 100 | assert.Equal(t, test.result, compareLT(test.first, test.second)) 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Reference](https://pkg.go.dev/badge/github.com/github/go-spdx/v2@v2.3.5/spdxexp.svg)](https://pkg.go.dev/github.com/github/go-spdx/v2@v2.3.5/spdxexp) spdxexp
2 | [![Go Reference](https://pkg.go.dev/badge/github.com/github/go-spdx/v2@v2.3.5/spdxexp/spdxlicenses.svg)](https://pkg.go.dev/github.com/github/go-spdx/v2@v2.3.5/spdxexp/spdxlicenses) spdxlicenses 3 | 4 | # go-spdx 5 | 6 | Golang implementation of a checker for determining if a set of SPDX IDs satisfies an SPDX Expression. 7 | 8 | ## Installation 9 | 10 | There are several ways to include a go package. To download and install, you can use `go get`. The command for that is: 11 | 12 | ```sh 13 | go get github.com/github/go-spdx@latest 14 | ``` 15 | 16 | ## Packages 17 | 18 | - [spdxexp](https://pkg.go.dev/github.com/github/go-spdx/spdxexp) - Expression package validates licenses and determines if a license expression is satisfied by a list of licenses. Validity of a license is determined by the SPDX license list. 19 | 20 | ## Public API 21 | 22 | _NOTE: The public API is initially limited to the Satisfies and ValidateLicenses functions. If 23 | there is interest in the output of the parser or license checking being public, please submit an 24 | issue for consideration._ 25 | 26 | ### Function: Satisfies 27 | 28 | ```go 29 | Satisfies(testExpression string, allowedList []string, options *Options) 30 | ``` 31 | 32 | **Parameter: testExpression** 33 | 34 | testExpression is an [SPDX expression](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/#d1-overview) describing the licensing terms of source code or a binary file. 35 | 36 | Example expressions that can be used for testExpression: 37 | 38 | ```go 39 | "MIT" 40 | "MIT AND Apache-2.0" 41 | "MIT OR Apache-2.0" 42 | "MIT AND (Apache-1.0 OR Apache-2.0)" 43 | "Apache-1.0+" 44 | "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2" 45 | "GPL-2.0 WITH Bison-exception-2.2" 46 | ``` 47 | 48 | _See satisfies_test.go for more example expressions._ 49 | 50 | **Parameter: allowedList** 51 | 52 | allowedList is an array of single licenses describing what licenses can be used to satisfy the testExpression. 53 | 54 | Example allowedList: 55 | 56 | ```go 57 | []string{"MIT"} 58 | []string{"MIT", "Apache-2.0"} 59 | []string{"MIT", "Apache-2.0", "ISC", "GPL-2.0"} 60 | []string{"MIT", "Apache-1.0+"} 61 | []string{"GPL-2.0-or-later"} 62 | ``` 63 | 64 | **N.B.** If at least one of expressions from `allowedList` is not a valid SPDX expression, the call 65 | to `Satisfies` will produce an error. Use [`ValidateLicenses`](###-ValidateLicenses) function 66 | to first check if all of the expressions from `allowedList` are valid. 67 | 68 | #### Examples: Satisfies returns true 69 | 70 | [Go Playground for Satisfies](https://go.dev/play/p/Ul8H15hyEpQ) 71 | 72 | ```go 73 | Satisfies("MIT", []string{"MIT"}) 74 | Satisfies("MIT", []string{"MIT", "Apache-2.0"}) 75 | Satisfies("Apache-2.0", []string{"Apache-1.0+"}) 76 | Satisfies("MIT OR Apache-2.0", []string{"Apache-2.0"}) 77 | Satisfies("MIT OR Apache-2.0", []string{"MIT", "Apache-2.0"}) 78 | Satisfies("MIT AND Apache-2.0", []string{"MIT", "Apache-2.0"}) 79 | Satisfies("MIT AND Apache-2.0", []string{"MIT", "Apache-2.0", "GPL-2.0"}) 80 | ``` 81 | 82 | #### Examples: Satisfies returns false 83 | 84 | ```go 85 | Satisfies("MIT", []string{"Apache-2.0"}) 86 | Satisfies("Apache-1.0", []string{"Apache-2.0+"}) 87 | Satisfies("MIT AND Apache-2.0", []string{"MIT"}) 88 | ``` 89 | 90 | ### ValidateLicenses 91 | 92 | ```go 93 | func ValidateLicenses(licenses []string) (bool, []string) 94 | ``` 95 | 96 | Function `ValidateLicenses` is used to determine if any of the provided license expressions is 97 | invalid. 98 | 99 | **parameter: licenses** 100 | 101 | Licenses is a slice of strings which must be validated as SPDX expressions. 102 | 103 | **returns** 104 | 105 | Function `ValidateLicenses` has 2 return values. First is `bool` which equals `true` if all of 106 | the provided licenses provided are valid, and `false` otherwise. 107 | 108 | The second parameter is a slice of all invalid licenses which were provided. 109 | 110 | #### Examples: ValidateLicenses returns no invalid licenses 111 | 112 | ```go 113 | valid, invalidLicenses := ValidateLicenses([]string{"Apache-2.0"}) 114 | assert.True(valid) 115 | assert.Empty(invalidLicenses) 116 | ``` 117 | 118 | #### Examples: ValidateLicenses returns invalid licenses 119 | 120 | ```go 121 | valid, invalidLicenses := ValidateLicenses([]string{"NON-EXISTENT-LICENSE", "MIT"}) 122 | assert.False(valid) 123 | assert.Contains(invalidLicenses, "NON-EXISTENT-LICENSE") 124 | assert.NotContains(invalidLicenses, "MIT") 125 | ``` 126 | 127 | #### Examples: ValidateLicenses works with SPDX expressions 128 | 129 | ```go 130 | valid, invalidLicenses := ValidateLicenses([]string{"MIT AND APACHE-2.0"}) 131 | assert.True(valid) 132 | assert.NotContains(invalidLicenses, "MIT AND APACHE-2.0") 133 | ``` 134 | 135 | ### ExtractLicenses 136 | 137 | ```go 138 | func ExtractLicenses(expression string) ([]string, error) 139 | ``` 140 | 141 | Function `ExtractLicenses` is used to extract licenses from the given expression without duplicates. 142 | 143 | **parameter: expression** 144 | 145 | `expression` is an SPDX expression string. 146 | 147 | **returns** 148 | 149 | Function `ExtractLicenses` has 2 return values. First is `[]string` which contains all of the SPDX licenses without duplicates. 150 | 151 | The second return value is an `error` which is not `nil` if the given expression is not a valid SPDX expression. 152 | 153 | #### Example 154 | 155 | ```go 156 | licenses, err := ExtractLicenses("(MIT AND APACHE-2.0) OR (APACHE-2.0)") 157 | assert.Equal(licenses, []string{"MIT", "Apache-2.0"}) 158 | ``` 159 | 160 | ## Background 161 | 162 | This package was developed to support testing whether a repository's license requirements are met by an allowed-list of licenses. 163 | 164 | Dependencies are defined in [go.mod](./go.mod). 165 | 166 | Contributions and requests are welcome. Refer to the [Contributing](#contributing) section for more information including how to set up a test environment and install dependencies. 167 | 168 | ## License 169 | 170 | This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE.md) for the full terms. 171 | 172 | ## Maintainers 173 | 174 | - @elrayle 175 | - @dangoor 176 | 177 | ## Support 178 | 179 | You can expect the following support: 180 | 181 | - bug fixes 182 | - review of feature request issues 183 | - review of questions in discussions 184 | 185 | ## Contributing 186 | 187 | Contributions in the form of bug identification Issues, bug fix PRs, and feature requests are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information on how to get involved and set up a testing environment. 188 | 189 | _NOTE: The list of valid licenses is maintained manually. If you notice a missing license, an excellent way to contribute to the long term viability of this package is to open an Issue or PR addressing the missing license._ 190 | 191 | ## Acknowledgement 192 | 193 | The process for parsing and evaluating expressions is a translation from JavaScript to Go based heavily on the JavaScript implementation defined across several repositories. 194 | 195 | - [spdx-satisfies.js](https://github.com/clearlydefined/spdx-satisfies.js) 196 | - [spdx-expression-parse.js](https://github.com/clearlydefined/spdx-expression-parse.js) 197 | - [spdx-ranges](https://github.com/jslicense/spdx-ranges.js) 198 | - [spdx-compare](https://github.com/jslicense/spdx-compare.js) 199 | - [spdx-license-ids](https://github.com/jslicense/spdx-license-ids) 200 | - [spdx-exceptions](https://github.com/jslicense/spdx-exceptions.json) 201 | -------------------------------------------------------------------------------- /spdxexp/node_test.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestReconstructedLicenseString(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | node *node 13 | result string 14 | }{ 15 | {"License node - simple", getLicenseNode("MIT", false), "MIT"}, 16 | {"License node - plus", getLicenseNode("Apache-1.0", true), "Apache-1.0+"}, 17 | {"License node - exception", 18 | &node{ 19 | role: licenseNode, 20 | exp: nil, 21 | lic: &licenseNodePartial{ 22 | license: "GPL-2.0", hasPlus: false, 23 | hasException: true, exception: "Bison-exception-2.2"}, 24 | ref: nil, 25 | }, "GPL-2.0 WITH Bison-exception-2.2"}, 26 | {"LicenseRef node - simple", 27 | &node{ 28 | role: licenseRefNode, 29 | exp: nil, 30 | lic: nil, 31 | ref: &referenceNodePartial{ 32 | hasDocumentRef: false, 33 | documentRef: "", 34 | licenseRef: "MIT-Style-2", 35 | }, 36 | }, "LicenseRef-MIT-Style-2"}, 37 | {"LicenseRef node - with DocumentRef", 38 | &node{ 39 | role: licenseRefNode, 40 | exp: nil, 41 | lic: nil, 42 | ref: &referenceNodePartial{ 43 | hasDocumentRef: true, 44 | documentRef: "spdx-tool-1.2", 45 | licenseRef: "MIT-Style-2", 46 | }, 47 | }, "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"}, 48 | } 49 | 50 | for _, test := range tests { 51 | test := test 52 | t.Run(test.name, func(t *testing.T) { 53 | license := *test.node.reconstructedLicenseString() 54 | assert.Equal(t, test.result, license) 55 | }) 56 | } 57 | } 58 | 59 | func TestLicensesAreCompatible(t *testing.T) { 60 | tests := []struct { 61 | name string 62 | nodes *nodePair 63 | result bool 64 | }{ 65 | {"compatible (exact equal): GPL-3.0, GPL-3.0", &nodePair{ 66 | getLicenseNode("GPL-3.0", false), 67 | getLicenseNode("GPL-3.0", false)}, true}, 68 | {"compatible (diff case equal): Apache-2.0, APACHE-2.0", &nodePair{ 69 | getLicenseNode("Apache-2.0", false), 70 | getLicenseNode("APACHE-2.0", false)}, true}, 71 | {"compatible (same version with +): Apache-1.0+, Apache-1.0", &nodePair{ 72 | getLicenseNode("Apache-1.0", true), 73 | getLicenseNode("Apache-1.0", false)}, true}, 74 | {"compatible (later version with +): Apache-1.0+, Apache-2.0", &nodePair{ 75 | getLicenseNode("Apache-1.0", true), 76 | getLicenseNode("Apache-2.0", false)}, true}, 77 | {"compatible (second version with +): Apache-2.0, Apache-1.0+", &nodePair{ 78 | getLicenseNode("Apache-2.0", false), 79 | getLicenseNode("Apache-1.0", true)}, true}, 80 | {"compatible (later version with both +): Apache-1.0+, Apache-2.0+", &nodePair{ 81 | getLicenseNode("Apache-1.0", true), 82 | getLicenseNode("Apache-2.0", true)}, true}, 83 | {"compatible (same version with -or-later): GPL-2.0-or-later, GPL-2.0", &nodePair{ 84 | getLicenseNode("GPL-2.0-or-later", true), 85 | getLicenseNode("GPL-2.0", false)}, true}, 86 | {"compatible (same version with -or-later and -only): GPL-2.0-or-later, GPL-2.0-only", &nodePair{ 87 | getLicenseNode("GPL-2.0-or-later", true), 88 | getLicenseNode("GPL-2.0-only", false)}, true}, // TODO: Double check that -or-later and -only should be true for GT 89 | {"compatible (later version with -or-later): GPL-2.0-or-later, GPL-3.0", &nodePair{ 90 | getLicenseNode("GPL-2.0-or-later", true), 91 | getLicenseNode("GPL-3.0", false)}, true}, 92 | {"incompatible (same version with -or-later exception): GPL-2.0, GPL-2.0-or-later WITH Bison-exception-2.2", &nodePair{ 93 | getLicenseNode("GPL-2.0", true), 94 | &node{ 95 | role: licenseNode, 96 | exp: nil, 97 | lic: &licenseNodePartial{ 98 | license: "GPL-2.0", hasPlus: true, 99 | hasException: true, exception: "Bison-exception-2.2"}, 100 | ref: nil, 101 | }}, false}, 102 | {"incompatible (different versions using -only): GPL-3.0-only, GPL-2.0-only", &nodePair{ 103 | getLicenseNode("GPL-3.0-only", false), 104 | getLicenseNode("GPL-2.0-only", false)}, false}, 105 | {"incompatible (different versions with letter): LPPL-1.3c, LPPL-1.3a", &nodePair{ 106 | getLicenseNode("LPPL-1.3c", false), 107 | getLicenseNode("LPPL-1.3a", false)}, false}, 108 | {"incompatible (first > second): AGPL-3.0, AGPL-1.0", &nodePair{ 109 | getLicenseNode("AGPL-3.0", false), 110 | getLicenseNode("AGPL-1.0", false)}, false}, 111 | {"incompatible (second > first): MPL-1.0, MPL-2.0", &nodePair{ 112 | getLicenseNode("MPL-1.0", false), 113 | getLicenseNode("MPL-2.0", false)}, false}, 114 | {"incompatible (diff licenses): MIT, ISC", &nodePair{ 115 | getLicenseNode("MIT", false), 116 | getLicenseNode("ISC", false)}, false}, 117 | {"not simple license: (MIT OR ISC), GPL-3.0", &nodePair{ 118 | getParsedNode("(MIT OR ISC)"), 119 | getLicenseNode("GPL-3.0", false)}, false}, 120 | } 121 | 122 | for _, test := range tests { 123 | test := test 124 | t.Run(test.name, func(t *testing.T) { 125 | assert.Equal(t, test.result, test.nodes.licensesAreCompatible()) 126 | }) 127 | } 128 | } 129 | 130 | func TestRangesAreCompatible(t *testing.T) { 131 | tests := []struct { 132 | name string 133 | nodes *nodePair 134 | result bool 135 | }{ 136 | {"compatible - both use -or-later", &nodePair{ 137 | firstNode: getLicenseNode("GPL-1.0-or-later", true), 138 | secondNode: getLicenseNode("GPL-2.0-or-later", true)}, true}, 139 | {"compatible - both use +", &nodePair{ 140 | firstNode: getLicenseNode("Apache-1.0", true), 141 | secondNode: getLicenseNode("Apache-2.0", true)}, true}, 142 | {"not compatible", &nodePair{ 143 | firstNode: getLicenseNode("GPL-1.0-or-later", true), 144 | secondNode: getLicenseNode("LGPL-3.0-or-later", true)}, false}, 145 | } 146 | 147 | for _, test := range tests { 148 | test := test 149 | t.Run(test.name, func(t *testing.T) { 150 | assert.Equal(t, test.result, test.nodes.rangesAreCompatible()) 151 | }) 152 | } 153 | } 154 | 155 | func TestIdentifierInRange(t *testing.T) { 156 | tests := []struct { 157 | name string 158 | nodes *nodePair 159 | result bool 160 | }{ 161 | {"in or-later range (later)", &nodePair{ 162 | firstNode: getLicenseNode("GPL-3.0", false), 163 | secondNode: getLicenseNode("GPL-2.0-or-later", true)}, true}, 164 | {"in or-later range (same)", &nodePair{ 165 | firstNode: getLicenseNode("GPL-2.0", false), 166 | secondNode: getLicenseNode("GPL-2.0-or-later", true)}, true}, 167 | {"in + range (1.0+)", &nodePair{ 168 | firstNode: getLicenseNode("Apache-2.0", false), 169 | secondNode: getLicenseNode("Apache-1.0", true)}, true}, 170 | {"not in range", &nodePair{ 171 | firstNode: getLicenseNode("GPL-1.0", false), 172 | secondNode: getLicenseNode("GPL-2.0-or-later", true)}, false}, 173 | {"different base license", &nodePair{ 174 | firstNode: getLicenseNode("GPL-1.0", false), 175 | secondNode: getLicenseNode("LGPL-2.0-or-later", true)}, false}, 176 | } 177 | 178 | for _, test := range tests { 179 | test := test 180 | t.Run(test.name, func(t *testing.T) { 181 | assert.Equal(t, test.result, test.nodes.identifierInRange()) 182 | }) 183 | } 184 | } 185 | 186 | func TestLicensesExactlyEqual(t *testing.T) { 187 | tests := []struct { 188 | name string 189 | nodes *nodePair 190 | result bool 191 | }{ 192 | {"equal", &nodePair{ 193 | firstNode: getLicenseNode("GPL-2.0", false), 194 | secondNode: getLicenseNode("GPL-2.0", false)}, true}, 195 | {"not equal", &nodePair{ 196 | firstNode: getLicenseNode("GPL-1.0", false), 197 | secondNode: getLicenseNode("GPL-2.0", false)}, false}, 198 | } 199 | 200 | for _, test := range tests { 201 | test := test 202 | t.Run(test.name, func(t *testing.T) { 203 | assert.Equal(t, test.result, test.nodes.licensesExactlyEqual()) 204 | }) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /spdxexp/spdxlicenses/license_ranges.go: -------------------------------------------------------------------------------- 1 | package spdxlicenses 2 | 3 | // licenseRanges returns a list of license ranges. 4 | // 5 | // Ranges are organized into groups (referred to as license groups) of the same base license (e.g. GPL). 6 | // Groups have sub-groups of license versions (referred to as the range) where each member is considered 7 | // to be the same version (e.g. {GPL-2.0, GPL-2.0-only}). The sub-groups are in ascending order within 8 | // the license group, such that the first sub-group is considered to be less than the second sub-group, 9 | // and so on. (e.g. {{GPL-1.0}, {GPL-2.0, GPL-2.0-only}} implies {GPL-1.0} < {GPL-2.0, GPL-2.0-only}). 10 | func LicenseRanges() [][][]string { 11 | return [][][]string{ 12 | { 13 | { 14 | "AFL-1.1", 15 | }, 16 | { 17 | "AFL-1.2", 18 | }, 19 | { 20 | "AFL-2.0", 21 | }, 22 | { 23 | "AFL-2.1", 24 | }, 25 | { 26 | "AFL-3.0", 27 | }, 28 | }, 29 | { 30 | { 31 | "AGPL-1.0", 32 | }, 33 | { 34 | "AGPL-3.0", 35 | "AGPL-3.0-only", 36 | }, 37 | }, 38 | { 39 | { 40 | "Apache-1.0", 41 | }, 42 | { 43 | "Apache-1.1", 44 | }, 45 | { 46 | "Apache-2.0", 47 | }, 48 | }, 49 | { 50 | { 51 | "APSL-1.0", 52 | }, 53 | { 54 | "APSL-1.1", 55 | }, 56 | { 57 | "APSL-1.2", 58 | }, 59 | { 60 | "APSL-2.0", 61 | }, 62 | }, 63 | { 64 | { 65 | "Artistic-1.0", 66 | }, 67 | { 68 | "Artistic-2.0", 69 | }, 70 | }, 71 | { 72 | { 73 | "ASWF-Digital-Assets-1.0", 74 | }, 75 | { 76 | "ASWF-Digital-Assets-1.1", 77 | }, 78 | }, 79 | { 80 | { 81 | "BitTorrent-1.0", 82 | }, 83 | { 84 | "BitTorrent-1.1", 85 | }, 86 | }, 87 | { 88 | { 89 | "Brian-Gladman-2-Clause", 90 | }, 91 | { 92 | "Brian-Gladman-3-Clause", 93 | }, 94 | }, 95 | { 96 | { 97 | "CC-BY-1.0", 98 | }, 99 | { 100 | "CC-BY-2.0", 101 | }, 102 | { 103 | "CC-BY-2.5", 104 | }, 105 | { 106 | "CC-BY-3.0", 107 | }, 108 | { 109 | "CC-BY-4.0", 110 | }, 111 | }, 112 | { 113 | { 114 | "CC-BY-NC-1.0", 115 | }, 116 | { 117 | "CC-BY-NC-2.0", 118 | }, 119 | { 120 | "CC-BY-NC-2.5", 121 | }, 122 | { 123 | "CC-BY-NC-3.0", 124 | }, 125 | { 126 | "CC-BY-NC-4.0", 127 | }, 128 | }, 129 | { 130 | { 131 | "CC-BY-NC-ND-1.0", 132 | }, 133 | { 134 | "CC-BY-NC-ND-2.0", 135 | }, 136 | { 137 | "CC-BY-NC-ND-2.5", 138 | }, 139 | { 140 | "CC-BY-NC-ND-3.0", 141 | }, 142 | { 143 | "CC-BY-NC-ND-4.0", 144 | }, 145 | }, 146 | { 147 | { 148 | "CC-BY-NC-SA-1.0", 149 | }, 150 | { 151 | "CC-BY-NC-SA-2.0", 152 | }, 153 | { 154 | "CC-BY-NC-SA-2.5", 155 | }, 156 | { 157 | "CC-BY-NC-SA-3.0", 158 | }, 159 | { 160 | "CC-BY-NC-SA-4.0", 161 | }, 162 | }, 163 | { 164 | { 165 | "CC-BY-ND-1.0", 166 | }, 167 | { 168 | "CC-BY-ND-2.0", 169 | }, 170 | { 171 | "CC-BY-ND-2.5", 172 | }, 173 | { 174 | "CC-BY-ND-3.0", 175 | }, 176 | { 177 | "CC-BY-ND-4.0", 178 | }, 179 | }, 180 | { 181 | { 182 | "CC-BY-SA-1.0", 183 | }, 184 | { 185 | "CC-BY-SA-2.0", 186 | }, 187 | { 188 | "CC-BY-SA-2.5", 189 | }, 190 | { 191 | "CC-BY-SA-3.0", 192 | }, 193 | { 194 | "CC-BY-SA-4.0", 195 | }, 196 | }, 197 | { 198 | { 199 | "CDDL-1.0", 200 | }, 201 | { 202 | "CDDL-1.1", 203 | }, 204 | }, 205 | { 206 | { 207 | "CECILL-1.0", 208 | }, 209 | { 210 | "CECILL-1.1", 211 | }, 212 | { 213 | "CECILL-2.0", 214 | }, 215 | }, 216 | { 217 | { 218 | "DRL-1.0", 219 | }, 220 | { 221 | "DRL-1.1", 222 | }, 223 | }, 224 | { 225 | { 226 | "ECL-1.0", 227 | }, 228 | { 229 | "ECL-2.0", 230 | }, 231 | }, 232 | { 233 | { 234 | "EFL-1.0", 235 | }, 236 | { 237 | "EFL-2.0", 238 | }, 239 | }, 240 | { 241 | { 242 | "EPL-1.0", 243 | }, 244 | { 245 | "EPL-2.0", 246 | }, 247 | }, 248 | { 249 | { 250 | "EUPL-1.0", 251 | }, 252 | { 253 | "EUPL-1.1", 254 | }, 255 | }, 256 | { 257 | { 258 | "GFDL-1.1", 259 | "GFDL-1.1-only", 260 | }, 261 | { 262 | "GFDL-1.2", 263 | "GFDL-1.2-only", 264 | }, 265 | { 266 | "GFDL-1.1-or-later", 267 | "GFDL-1.2-or-later", 268 | "GFDL-1.3", 269 | "GFDL-1.3-only", 270 | "GFDL-1.3-or-later", 271 | }, 272 | }, 273 | { 274 | { 275 | "GPL-1.0", 276 | "GPL-1.0-only", 277 | }, 278 | { 279 | "GPL-2.0", 280 | "GPL-2.0-only", 281 | }, 282 | { 283 | "GPL-1.0-or-later", 284 | "GPL-2.0-or-later", 285 | "GPL-3.0", 286 | "GPL-3.0-only", 287 | "GPL-3.0-or-later", 288 | }, 289 | }, 290 | { 291 | { 292 | "HP-1986", 293 | }, 294 | { 295 | "HP-1989", 296 | }, 297 | }, 298 | { 299 | { 300 | "LGPL-2.0", 301 | "LGPL-2.0-only", 302 | }, 303 | { 304 | "LGPL-2.1", 305 | "LGPL-2.1-only", 306 | }, 307 | { 308 | "LGPL-2.0-or-later", 309 | "LGPL-2.1-or-later", 310 | "LGPL-3.0", 311 | "LGPL-3.0-only", 312 | "LGPL-3.0-or-later", 313 | }, 314 | }, 315 | { 316 | { 317 | "LPL-1.0", 318 | }, 319 | { 320 | "LPL-1.02", 321 | }, 322 | }, 323 | { 324 | { 325 | "LPPL-1.0", 326 | }, 327 | { 328 | "LPPL-1.1", 329 | }, 330 | { 331 | "LPPL-1.2", 332 | }, 333 | { 334 | "LPPL-1.3a", 335 | }, 336 | { 337 | "LPPL-1.3c", 338 | }, 339 | }, 340 | { 341 | { 342 | "MPL-1.0", 343 | }, 344 | { 345 | "MPL-1.1", 346 | }, 347 | { 348 | "MPL-2.0", 349 | }, 350 | }, 351 | { 352 | { 353 | "MPL-1.0", 354 | }, 355 | { 356 | "MPL-1.1", 357 | }, 358 | { 359 | "MPL-2.0-no-copyleft-exception", 360 | }, 361 | }, 362 | { 363 | { 364 | "NPL-1.0", 365 | }, 366 | { 367 | "NPL-1.1", 368 | }, 369 | }, 370 | { 371 | { 372 | "OFL-1.0", 373 | }, 374 | { 375 | "OFL-1.1", 376 | }, 377 | }, 378 | { 379 | { 380 | "OLDAP-1.1", 381 | }, 382 | { 383 | "OLDAP-1.2", 384 | }, 385 | { 386 | "OLDAP-1.3", 387 | }, 388 | { 389 | "OLDAP-1.4", 390 | }, 391 | { 392 | "OLDAP-2.0", 393 | }, 394 | { 395 | "OLDAP-2.0.1", 396 | }, 397 | { 398 | "OLDAP-2.1", 399 | }, 400 | { 401 | "OLDAP-2.2", 402 | }, 403 | { 404 | "OLDAP-2.2.1", 405 | }, 406 | { 407 | "OLDAP-2.2.2", 408 | }, 409 | { 410 | "OLDAP-2.3", 411 | }, 412 | { 413 | "OLDAP-2.4", 414 | }, 415 | { 416 | "OLDAP-2.5", 417 | }, 418 | { 419 | "OLDAP-2.6", 420 | }, 421 | { 422 | "OLDAP-2.7", 423 | }, 424 | { 425 | "OLDAP-2.8", 426 | }, 427 | }, 428 | { 429 | { 430 | "OSL-1.0", 431 | }, 432 | { 433 | "OSL-1.1", 434 | }, 435 | { 436 | "OSL-2.0", 437 | }, 438 | { 439 | "OSL-2.1", 440 | }, 441 | { 442 | "OSL-3.0", 443 | }, 444 | }, 445 | { 446 | { 447 | "PHP-3.0", 448 | }, 449 | { 450 | "PHP-3.01", 451 | }, 452 | }, 453 | { 454 | { 455 | "RPL-1.1", 456 | }, 457 | { 458 | "RPL-1.5", 459 | }, 460 | }, 461 | { 462 | { 463 | "SGI-B-1.0", 464 | }, 465 | { 466 | "SGI-B-1.1", 467 | }, 468 | { 469 | "SGI-B-2.0", 470 | }, 471 | }, 472 | { 473 | { 474 | "YPL-1.0", 475 | }, 476 | { 477 | "YPL-1.1", 478 | }, 479 | }, 480 | { 481 | { 482 | "ZPL-1.1", 483 | }, 484 | { 485 | "ZPL-2.0", 486 | }, 487 | { 488 | "ZPL-2.1", 489 | }, 490 | }, 491 | { 492 | { 493 | "Zimbra-1.3", 494 | }, 495 | { 496 | "Zimbra-1.4", 497 | }, 498 | }, 499 | { 500 | { 501 | "bzip2-1.0.5", 502 | }, 503 | { 504 | "bzip2-1.0.6", 505 | }, 506 | }, 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /spdxexp/scan.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | /* Translation to Go from javascript code: https://github.com/clearlydefined/spdx-expression-parse.js/blob/master/scan.js */ 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | type expressionStream struct { 13 | expression string 14 | index int 15 | err error 16 | } 17 | 18 | type token struct { 19 | role tokenrole 20 | value string 21 | } 22 | 23 | type tokenrole uint8 24 | 25 | const ( 26 | operatorToken tokenrole = iota 27 | documentRefToken 28 | licenseRefToken 29 | licenseToken 30 | exceptionToken 31 | ) 32 | 33 | // Scan scans a string expression gathering valid SPDX expression tokens. Returns error if any tokens are invalid. 34 | func scan(expression string) ([]token, error) { 35 | var tokens []token 36 | var token *token 37 | 38 | exp := &expressionStream{expression: expression, index: 0, err: nil} 39 | 40 | for exp.hasMore() { 41 | exp.skipWhitespace() 42 | if !exp.hasMore() { 43 | break 44 | } 45 | 46 | token = exp.parseToken() 47 | if exp.err != nil { 48 | // stop processing at first error and return 49 | return nil, exp.err 50 | } 51 | 52 | if token == nil { 53 | // TODO: shouldn't happen ??? 54 | return nil, errors.New("got nil token when expecting more") 55 | } 56 | 57 | tokens = append(tokens, *token) 58 | } 59 | return tokens, nil 60 | } 61 | 62 | // Determine if expression has more to process. 63 | func (exp *expressionStream) hasMore() bool { 64 | return exp.index < len(exp.expression) 65 | } 66 | 67 | // Try to read the next token starting at index. Returns error if no token is recognized. 68 | func (exp *expressionStream) parseToken() *token { 69 | // Ordering matters 70 | op := exp.readOperator() 71 | if exp.err != nil { 72 | return nil 73 | } 74 | if op != nil { 75 | return op 76 | } 77 | 78 | dref := exp.readDocumentRef() 79 | if exp.err != nil { 80 | return nil 81 | } 82 | if dref != nil { 83 | return dref 84 | } 85 | 86 | lref := exp.readLicenseRef() 87 | if exp.err != nil { 88 | return nil 89 | } 90 | if lref != nil { 91 | return lref 92 | } 93 | 94 | identifier := exp.readLicense() 95 | if exp.err != nil { 96 | return nil 97 | } 98 | if identifier != nil { 99 | return identifier 100 | } 101 | 102 | errmsg := fmt.Sprintf("unexpected '%c' at offset %d", exp.expression[exp.index], exp.index) 103 | exp.err = errors.New(errmsg) 104 | return nil 105 | } 106 | 107 | // Read more from expression if the next substring starting at index matches the regex pattern. 108 | func (exp *expressionStream) readRegex(pattern string) string { 109 | expressionSlice := exp.expression[exp.index:] 110 | 111 | r, _ := regexp.Compile(pattern) 112 | i := r.FindStringIndex(expressionSlice) 113 | if i != nil && i[1] > 0 && i[0] == 0 { 114 | // match found in expression at index 115 | exp.index += i[1] 116 | return expressionSlice[0:i[1]] 117 | } 118 | return "" 119 | } 120 | 121 | // Read more from expression if the substring starting at index is the next expected string. 122 | func (exp *expressionStream) read(next string) string { 123 | expressionSlice := exp.expression[exp.index:] 124 | 125 | if strings.HasPrefix(expressionSlice, next) { 126 | // next found in expression at index 127 | exp.index += len(next) 128 | return next 129 | } 130 | return "" 131 | } 132 | 133 | // Skip whitespace in expression starting at index 134 | func (exp *expressionStream) skipWhitespace() { 135 | exp.readRegex("[ ]*") 136 | } 137 | 138 | // Read operator in expression starting at index if it exists 139 | func (exp *expressionStream) readOperator() *token { 140 | possibilities := []string{"WITH", "AND", "OR", "(", ")", ":", "+"} 141 | 142 | var op string 143 | for _, p := range possibilities { 144 | op = exp.read(p) 145 | if len(op) > 0 { 146 | break 147 | } 148 | } 149 | if len(op) == 0 { 150 | // not an error if an operator isn't found 151 | return nil 152 | } 153 | 154 | if op == "+" && exp.index > 1 && exp.expression[exp.index-2:exp.index-1] == " " { 155 | exp.err = errors.New("unexpected space before +") 156 | exp.index-- 157 | return nil 158 | } 159 | 160 | return &token{role: operatorToken, value: op} 161 | } 162 | 163 | // Get id from expression starting at index. Raise error if id not found. 164 | func (exp *expressionStream) readID() string { 165 | id := exp.readRegex("[A-Za-z0-9-.]+") 166 | if len(id) == 0 { 167 | errmsg := fmt.Sprintf("expected id at offset %d", exp.index) 168 | exp.err = errors.New(errmsg) 169 | return "" 170 | } 171 | return id 172 | } 173 | 174 | // Read DocumentRef in expression starting at index if it exists. Raise error if found and id doesn't follow. 175 | func (exp *expressionStream) readDocumentRef() *token { 176 | ref := exp.read("DocumentRef-") 177 | if len(ref) == 0 { 178 | // not an error if a DocumentRef isn't found 179 | return nil 180 | } 181 | 182 | id := exp.readID() 183 | if exp.err != nil { 184 | return nil 185 | } 186 | return &token{role: documentRefToken, value: id} 187 | } 188 | 189 | // Read LicenseRef in expression starting at index if it exists. Raise error if found and id doesn't follow. 190 | func (exp *expressionStream) readLicenseRef() *token { 191 | ref := exp.read("LicenseRef-") 192 | if len(ref) == 0 { 193 | // not an error if a LicenseRef isn't found 194 | return nil 195 | } 196 | 197 | id := exp.readID() 198 | if exp.err != nil { 199 | return nil 200 | } 201 | return &token{role: licenseRefToken, value: id} 202 | } 203 | 204 | // Read a LICENSE/EXCEPTION in expression starting at index if it exists. Raise error if found and id doesn't follow. 205 | func (exp *expressionStream) readLicense() *token { 206 | // because readID matches broadly, save the index so it can be reset if an actual license is not found 207 | index := exp.index 208 | 209 | license := exp.readID() 210 | if exp.err != nil { 211 | return nil 212 | } 213 | 214 | if token := exp.normalizeLicense(license); token != nil { 215 | return token 216 | } 217 | 218 | // license not found in indices, need to reset index since readID advanced it 219 | exp.index = index 220 | errmsg := fmt.Sprintf("unknown license '%s' at offset %d", license, exp.index) 221 | exp.err = errors.New(errmsg) 222 | return nil 223 | } 224 | 225 | // Generate a token using the normalized form of the license name. 226 | // 227 | // License name can be in the form: 228 | // - a_license-2.0, a_license, a_license-ab - there is variability in the form of the base license. a_license-2.0 is used for these 229 | // examples, but any base license form can have the suffixes described. 230 | // - a_license-2.0-only - normalizes to a_license-2.0 if the -only form is not specifically in the set of licenses 231 | // - a_license-2.0-or-later - normalizes to a_license-2.0+ if the -or-later form is not specifically in the set of licenses 232 | // - a_license-2.0+ - normalizes to a_license-2.0-or-later if the -or-later form is specifically in the set of licenses 233 | func (exp *expressionStream) normalizeLicense(license string) *token { 234 | if token := licenseLookup(license); token != nil { 235 | // checks active and exception license lists 236 | // deprecated list is checked at the end to avoid a deprecated license being used for + 237 | // (example: GPL-1.0 is on the deprecated list, but GPL-1.0+ should become GPL-1.0-or-later) 238 | return token 239 | } 240 | 241 | lenLicense := len(license) 242 | if strings.HasSuffix(license, "-only") { 243 | adjustedLicense := license[0 : lenLicense-5] 244 | if token := licenseLookup(adjustedLicense); token != nil { 245 | // no need to remove the -only from the expression stream; it is ignored 246 | return token 247 | } 248 | } 249 | if exp.hasMore() && exp.expression[exp.index:exp.index+1] == "+" { 250 | adjustedLicense := license[0:lenLicense] + "-or-later" 251 | if token := licenseLookup(adjustedLicense); token != nil { 252 | // need to consume the + to avoid a + operator token being added 253 | exp.index++ 254 | return token 255 | } 256 | } 257 | if strings.HasSuffix(license, "-or-later") { 258 | adjustedLicense := license[0 : lenLicense-9] 259 | if token := licenseLookup(adjustedLicense); token != nil { 260 | // replace `-or-later` with `+` 261 | newExpression := exp.expression[0:exp.index-len("-or-later")] + "+" 262 | if exp.hasMore() { 263 | newExpression += exp.expression[exp.index+1:] 264 | } 265 | exp.expression = newExpression 266 | // update index to remove `-or-later`; now pointing at the `+` operator 267 | exp.index -= len("-or-later") 268 | 269 | return token 270 | } 271 | } 272 | 273 | return deprecatedLicenseLookup(license) 274 | } 275 | 276 | // Lookup license identifier in active and exception lists to determine if it is a supported SPDX id 277 | func licenseLookup(license string) *token { 278 | active, preferredLicense := activeLicense(license) 279 | if active { 280 | return &token{role: licenseToken, value: preferredLicense} 281 | } 282 | exception, preferredLicense := exceptionLicense(license) 283 | if exception { 284 | return &token{role: exceptionToken, value: preferredLicense} 285 | } 286 | return nil 287 | } 288 | 289 | // Lookup license identifier in deprecated list to determine if it is a supported SPDX id 290 | func deprecatedLicenseLookup(license string) *token { 291 | deprecated, preferredLicense := deprecatedLicense(license) 292 | if deprecated { 293 | return &token{role: licenseToken, value: preferredLicense} 294 | } 295 | return nil 296 | } 297 | -------------------------------------------------------------------------------- /spdxexp/node.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | ) 7 | 8 | type nodePair struct { 9 | firstNode *node 10 | secondNode *node 11 | } 12 | 13 | type nodeRole uint8 14 | 15 | const ( 16 | expressionNode nodeRole = iota 17 | licenseRefNode 18 | licenseNode 19 | ) 20 | 21 | type node struct { 22 | role nodeRole 23 | exp *expressionNodePartial 24 | lic *licenseNodePartial 25 | ref *referenceNodePartial 26 | } 27 | 28 | type expressionNodePartial struct { 29 | left *node 30 | conjunction string 31 | right *node 32 | } 33 | 34 | type licenseNodePartial struct { 35 | license string 36 | hasPlus bool 37 | hasException bool 38 | exception string 39 | } 40 | 41 | type referenceNodePartial struct { 42 | hasDocumentRef bool 43 | documentRef string 44 | licenseRef string 45 | } 46 | 47 | // ---------------------- Helper Methods ---------------------- 48 | 49 | func (n *node) isExpression() bool { 50 | return n.role == expressionNode 51 | } 52 | 53 | func (n *node) isOrExpression() bool { 54 | if !n.isExpression() { 55 | return false 56 | } 57 | return *n.conjunction() == "or" 58 | } 59 | 60 | func (n *node) isAndExpression() bool { 61 | if !n.isExpression() { 62 | return false 63 | } 64 | return *n.conjunction() == "and" 65 | } 66 | 67 | func (n *node) left() *node { 68 | if !n.isExpression() { 69 | return nil 70 | } 71 | return n.exp.left 72 | } 73 | 74 | func (n *node) conjunction() *string { 75 | if !n.isExpression() { 76 | return nil 77 | } 78 | return &(n.exp.conjunction) 79 | } 80 | 81 | func (n *node) right() *node { 82 | if !n.isExpression() { 83 | return nil 84 | } 85 | return n.exp.right 86 | } 87 | 88 | func (n *node) isLicense() bool { 89 | return n.role == licenseNode 90 | } 91 | 92 | // license returns the value of the license field. 93 | // See also reconstructedLicenseString() 94 | func (n *node) license() *string { 95 | if !n.isLicense() { 96 | return nil 97 | } 98 | return &(n.lic.license) 99 | } 100 | 101 | func (n *node) exception() *string { 102 | if !n.hasException() { 103 | return nil 104 | } 105 | return &(n.lic.exception) 106 | } 107 | 108 | func (n *node) hasPlus() bool { 109 | if !n.isLicense() { 110 | return false 111 | } 112 | return n.lic.hasPlus 113 | } 114 | 115 | func (n *node) hasException() bool { 116 | if !n.isLicense() { 117 | return false 118 | } 119 | return n.lic.hasException 120 | } 121 | 122 | func (n *node) isLicenseRef() bool { 123 | return n.role == licenseRefNode 124 | } 125 | 126 | func (n *node) licenseRef() *string { 127 | if !n.isLicenseRef() { 128 | return nil 129 | } 130 | return &(n.ref.licenseRef) 131 | } 132 | 133 | func (n *node) documentRef() *string { 134 | if !n.hasDocumentRef() { 135 | return nil 136 | } 137 | return &(n.ref.documentRef) 138 | } 139 | 140 | func (n *node) hasDocumentRef() bool { 141 | if !n.isLicenseRef() { 142 | return false 143 | } 144 | return n.ref.hasDocumentRef 145 | } 146 | 147 | // reconstructedLicenseString returns the string representation of the license or license ref. 148 | // TODO: Original had "NOASSERTION". Does that still apply? 149 | func (n *node) reconstructedLicenseString() *string { 150 | switch n.role { 151 | case licenseNode: 152 | license := *n.license() 153 | if n.hasPlus() { 154 | license += "+" 155 | } 156 | if n.hasException() { 157 | license += " WITH " + *n.exception() 158 | } 159 | return &license 160 | case licenseRefNode: 161 | license := "LicenseRef-" + *n.licenseRef() 162 | if n.hasDocumentRef() { 163 | license = "DocumentRef-" + *n.documentRef() + ":" + license 164 | } 165 | return &license 166 | } 167 | return nil 168 | } 169 | 170 | // sortLicenses sorts an array of license and license reference nodes alphabetically based 171 | // on their reconstructedLicenseString() representation. The sort function does not expect 172 | // expression nodes, but if one is in the nodes list, it will sort to the end. 173 | func sortLicenses(nodes []*node) { 174 | sort.Slice(nodes, func(i, j int) bool { 175 | if nodes[j].isExpression() { 176 | // push second license toward end by saying first license is less than 177 | return true 178 | } 179 | if nodes[i].isExpression() { 180 | // push first license toward end by saying second license is less than 181 | return false 182 | } 183 | return *nodes[i].reconstructedLicenseString() < *nodes[j].reconstructedLicenseString() 184 | }) 185 | } 186 | 187 | // ---------------------- Comparator Methods ---------------------- 188 | 189 | // licensesAreCompatible returns true if two licenses are compatible; otherwise, false. 190 | // Two licenses are compatible if they are the same license or if they are in the same 191 | // license group and they meet one of the following rules: 192 | // 193 | // * both licenses have the `hasPlus` flag set to true 194 | // * the first license has the `hasPlus` flag and the second license is in the first license's range or greater 195 | // * the second license has the `hasPlus` flag and the first license is in the second license's range or greater 196 | // * both licenses are in the same range 197 | func (nodes *nodePair) licensesAreCompatible() bool { 198 | // checking ranges is expensive, so check for simple cases first 199 | if !nodes.firstNode.isLicense() || !nodes.secondNode.isLicense() { 200 | return false 201 | } 202 | if !nodes.exceptionsAreCompatible() { 203 | return false 204 | } 205 | if nodes.licensesExactlyEqual() { 206 | return true 207 | } 208 | 209 | // simple cases don't apply, so check license ranges 210 | // NOTE: Ranges are organized into groups (referred to as license groups) of the same base license (e.g. GPL). 211 | // Groups have sub-groups of license versions (referred to as the range) where each member is considered 212 | // to be the same version (e.g. {GPL-2.0, GPL-2.0-only}). The sub-groups are in ascending order within 213 | // the license group, such that the first sub-group is considered to be less than the second sub-group, 214 | // and so on. (e.g. {{GPL-1.0}, {GPL-2.0, GPL-2.0-only}} implies {GPL-1.0} < {GPL-2.0, GPL-2.0-only}). 215 | if nodes.secondNode.hasPlus() { 216 | if nodes.firstNode.hasPlus() { 217 | // first+, second+ just need to be in same range group 218 | return nodes.rangesAreCompatible() 219 | } 220 | // first, second+ requires first to be in range of second 221 | return nodes.identifierInRange() 222 | } 223 | // else secondNode does not have plus 224 | if nodes.firstNode.hasPlus() { 225 | // first+, second requires second to be in range of first 226 | revNodes := &nodePair{firstNode: nodes.secondNode, secondNode: nodes.firstNode} 227 | return revNodes.identifierInRange() 228 | } 229 | // first, second requires both to be in same range group 230 | return nodes.rangesEqual() 231 | } 232 | 233 | // licenseRefsAreCompatible returns true if two license references are compatible; otherwise, false. 234 | func (nodes *nodePair) licenseRefsAreCompatible() bool { 235 | if !nodes.firstNode.isLicenseRef() || !nodes.secondNode.isLicenseRef() { 236 | return false 237 | } 238 | 239 | compatible := *nodes.firstNode.licenseRef() == *nodes.secondNode.licenseRef() 240 | compatible = compatible && (nodes.firstNode.hasDocumentRef() == nodes.secondNode.hasDocumentRef()) 241 | if compatible && nodes.firstNode.hasDocumentRef() { 242 | compatible = compatible && (*nodes.firstNode.documentRef() == *nodes.secondNode.documentRef()) 243 | } 244 | return compatible 245 | } 246 | 247 | // licenseRefsAreCompatible returns true if two licenses are in the same license group (e.g. all "GPL" licenses are in the same 248 | // license group); otherwise, false. 249 | func (nodes *nodePair) rangesAreCompatible() bool { 250 | firstNode := *nodes.firstNode 251 | secondNode := *nodes.secondNode 252 | 253 | firstRange := getLicenseRange(*firstNode.license()) 254 | secondRange := getLicenseRange(*secondNode.license()) 255 | 256 | // When both licenses allow later versions (i.e. hasPlus==true), being in the same license 257 | // group is sufficient for compatibility, as long as, any exception is also compatible 258 | // Example: All Apache licenses (e.g. Apache-1.0, Apache-2.0) are in the same license group 259 | return sameLicenseGroup(firstRange, secondRange) 260 | } 261 | 262 | // identifierInRange returns true if the (first) simple license is in range of the (second) 263 | // ranged license; otherwise, false. 264 | func (nodes *nodePair) identifierInRange() bool { 265 | simpleLicense := nodes.firstNode 266 | plusLicense := nodes.secondNode 267 | 268 | return compareGT(simpleLicense, plusLicense) || compareEQ(simpleLicense, plusLicense) 269 | } 270 | 271 | // exceptionsAreCompatible returns true if neither license has an exception or they have 272 | // the same exception; otherwise, false 273 | func (nodes *nodePair) exceptionsAreCompatible() bool { 274 | firstNode := *nodes.firstNode 275 | secondNode := *nodes.secondNode 276 | 277 | if !firstNode.hasException() && !secondNode.hasException() { 278 | // if neither has an exception, then licenses are compatible 279 | return true 280 | } 281 | 282 | if firstNode.hasException() != secondNode.hasException() { 283 | // if one has and exception and the other does not, then the license are NOT compatible 284 | return false 285 | } 286 | 287 | return *nodes.firstNode.exception() == *nodes.secondNode.exception() 288 | } 289 | 290 | // rangesEqual returns true if the licenses are in the same range; otherwise, false 291 | // (e.g. GPL-2.0-only == GPL-2.0) 292 | func (nodes *nodePair) rangesEqual() bool { 293 | return compareEQ(nodes.firstNode, nodes.secondNode) 294 | } 295 | 296 | // licensesExactlyEqual returns true if the licenses are the same; otherwise, false 297 | func (nodes *nodePair) licensesExactlyEqual() bool { 298 | return strings.EqualFold(*nodes.firstNode.reconstructedLicenseString(), *nodes.secondNode.reconstructedLicenseString()) 299 | } 300 | -------------------------------------------------------------------------------- /cmd/license_ids.txt: -------------------------------------------------------------------------------- 1 | "0BSD", 2 | "AAL", 3 | "Abstyles", 4 | "Adobe-2006", 5 | "Adobe-Glyph", 6 | "ADSL", 7 | "AFL-1.1", 8 | "AFL-1.2", 9 | "AFL-2.0", 10 | "AFL-2.1", 11 | "AFL-3.0", 12 | "Afmparse", 13 | "AGPL-1.0-only", 14 | "AGPL-1.0-or-later", 15 | "AGPL-3.0-only", 16 | "AGPL-3.0-or-later", 17 | "Aladdin", 18 | "AMDPLPA", 19 | "AML", 20 | "AMPAS", 21 | "ANTLR-PD", 22 | "ANTLR-PD-fallback", 23 | "Apache-1.0", 24 | "Apache-1.1", 25 | "Apache-2.0", 26 | "APAFML", 27 | "APL-1.0", 28 | "App-s2p", 29 | "APSL-1.0", 30 | "APSL-1.1", 31 | "APSL-1.2", 32 | "APSL-2.0", 33 | "Arphic-1999", 34 | "Artistic-1.0", 35 | "Artistic-1.0-cl8", 36 | "Artistic-1.0-Perl", 37 | "Artistic-2.0", 38 | "Baekmuk", 39 | "Bahyph", 40 | "Barr", 41 | "Beerware", 42 | "Bitstream-Charter", 43 | "Bitstream-Vera", 44 | "BitTorrent-1.0", 45 | "BitTorrent-1.1", 46 | "blessing", 47 | "BlueOak-1.0.0", 48 | "Borceux", 49 | "BSD-1-Clause", 50 | "BSD-2-Clause", 51 | "BSD-2-Clause-Patent", 52 | "BSD-2-Clause-Views", 53 | "BSD-3-Clause", 54 | "BSD-3-Clause-Attribution", 55 | "BSD-3-Clause-Clear", 56 | "BSD-3-Clause-LBNL", 57 | "BSD-3-Clause-Modification", 58 | "BSD-3-Clause-No-Military-License", 59 | "BSD-3-Clause-No-Nuclear-License", 60 | "BSD-3-Clause-No-Nuclear-License-2014", 61 | "BSD-3-Clause-No-Nuclear-Warranty", 62 | "BSD-3-Clause-Open-MPI", 63 | "BSD-4-Clause", 64 | "BSD-4-Clause-Shortened", 65 | "BSD-4-Clause-UC", 66 | "BSD-Protection", 67 | "BSD-Source-Code", 68 | "BSL-1.0", 69 | "BUSL-1.1", 70 | "bzip2-1.0.6", 71 | "C-UDA-1.0", 72 | "CAL-1.0", 73 | "CAL-1.0-Combined-Work-Exception", 74 | "Caldera", 75 | "CATOSL-1.1", 76 | "CC-BY-1.0", 77 | "CC-BY-2.0", 78 | "CC-BY-2.5", 79 | "CC-BY-2.5-AU", 80 | "CC-BY-3.0", 81 | "CC-BY-3.0-AT", 82 | "CC-BY-3.0-DE", 83 | "CC-BY-3.0-IGO", 84 | "CC-BY-3.0-NL", 85 | "CC-BY-3.0-US", 86 | "CC-BY-4.0", 87 | "CC-BY-NC-1.0", 88 | "CC-BY-NC-2.0", 89 | "CC-BY-NC-2.5", 90 | "CC-BY-NC-3.0", 91 | "CC-BY-NC-3.0-DE", 92 | "CC-BY-NC-4.0", 93 | "CC-BY-NC-ND-1.0", 94 | "CC-BY-NC-ND-2.0", 95 | "CC-BY-NC-ND-2.5", 96 | "CC-BY-NC-ND-3.0", 97 | "CC-BY-NC-ND-3.0-DE", 98 | "CC-BY-NC-ND-3.0-IGO", 99 | "CC-BY-NC-ND-4.0", 100 | "CC-BY-NC-SA-1.0", 101 | "CC-BY-NC-SA-2.0", 102 | "CC-BY-NC-SA-2.0-DE", 103 | "CC-BY-NC-SA-2.0-FR", 104 | "CC-BY-NC-SA-2.0-UK", 105 | "CC-BY-NC-SA-2.5", 106 | "CC-BY-NC-SA-3.0", 107 | "CC-BY-NC-SA-3.0-DE", 108 | "CC-BY-NC-SA-3.0-IGO", 109 | "CC-BY-NC-SA-4.0", 110 | "CC-BY-ND-1.0", 111 | "CC-BY-ND-2.0", 112 | "CC-BY-ND-2.5", 113 | "CC-BY-ND-3.0", 114 | "CC-BY-ND-3.0-DE", 115 | "CC-BY-ND-4.0", 116 | "CC-BY-SA-1.0", 117 | "CC-BY-SA-2.0", 118 | "CC-BY-SA-2.0-UK", 119 | "CC-BY-SA-2.1-JP", 120 | "CC-BY-SA-2.5", 121 | "CC-BY-SA-3.0", 122 | "CC-BY-SA-3.0-AT", 123 | "CC-BY-SA-3.0-DE", 124 | "CC-BY-SA-4.0", 125 | "CC-PDDC", 126 | "CC0-1.0", 127 | "CDDL-1.0", 128 | "CDDL-1.1", 129 | "CDL-1.0", 130 | "CDLA-Permissive-1.0", 131 | "CDLA-Permissive-2.0", 132 | "CDLA-Sharing-1.0", 133 | "CECILL-1.0", 134 | "CECILL-1.1", 135 | "CECILL-2.0", 136 | "CECILL-2.1", 137 | "CECILL-B", 138 | "CECILL-C", 139 | "CERN-OHL-1.1", 140 | "CERN-OHL-1.2", 141 | "CERN-OHL-P-2.0", 142 | "CERN-OHL-S-2.0", 143 | "CERN-OHL-W-2.0", 144 | "checkmk", 145 | "ClArtistic", 146 | "CNRI-Jython", 147 | "CNRI-Python", 148 | "CNRI-Python-GPL-Compatible", 149 | "COIL-1.0", 150 | "Community-Spec-1.0", 151 | "Condor-1.1", 152 | "copyleft-next-0.3.0", 153 | "copyleft-next-0.3.1", 154 | "CPAL-1.0", 155 | "CPL-1.0", 156 | "CPOL-1.02", 157 | "Crossword", 158 | "CrystalStacker", 159 | "CUA-OPL-1.0", 160 | "Cube", 161 | "curl", 162 | "D-FSL-1.0", 163 | "diffmark", 164 | "DL-DE-BY-2.0", 165 | "DOC", 166 | "Dotseqn", 167 | "DRL-1.0", 168 | "DSDP", 169 | "dvipdfm", 170 | "ECL-1.0", 171 | "ECL-2.0", 172 | "EFL-1.0", 173 | "EFL-2.0", 174 | "eGenix", 175 | "Elastic-2.0", 176 | "Entessa", 177 | "EPICS", 178 | "EPL-1.0", 179 | "EPL-2.0", 180 | "ErlPL-1.1", 181 | "etalab-2.0", 182 | "EUDatagrid", 183 | "EUPL-1.0", 184 | "EUPL-1.1", 185 | "EUPL-1.2", 186 | "Eurosym", 187 | "Fair", 188 | "FDK-AAC", 189 | "Frameworx-1.0", 190 | "FreeBSD-DOC", 191 | "FreeImage", 192 | "FSFAP", 193 | "FSFUL", 194 | "FSFULLR", 195 | "FSFULLRWD", 196 | "FTL", 197 | "GD", 198 | "GFDL-1.1-invariants-only", 199 | "GFDL-1.1-invariants-or-later", 200 | "GFDL-1.1-no-invariants-only", 201 | "GFDL-1.1-no-invariants-or-later", 202 | "GFDL-1.1-only", 203 | "GFDL-1.1-or-later", 204 | "GFDL-1.2-invariants-only", 205 | "GFDL-1.2-invariants-or-later", 206 | "GFDL-1.2-no-invariants-only", 207 | "GFDL-1.2-no-invariants-or-later", 208 | "GFDL-1.2-only", 209 | "GFDL-1.2-or-later", 210 | "GFDL-1.3-invariants-only", 211 | "GFDL-1.3-invariants-or-later", 212 | "GFDL-1.3-no-invariants-only", 213 | "GFDL-1.3-no-invariants-or-later", 214 | "GFDL-1.3-only", 215 | "GFDL-1.3-or-later", 216 | "Giftware", 217 | "GL2PS", 218 | "Glide", 219 | "Glulxe", 220 | "GLWTPL", 221 | "gnuplot", 222 | "GPL-1.0-only", 223 | "GPL-1.0-or-later", 224 | "GPL-2.0-only", 225 | "GPL-2.0-or-later", 226 | "GPL-3.0-only", 227 | "GPL-3.0-or-later", 228 | "Graphics-Gems", 229 | "gSOAP-1.3b", 230 | "HaskellReport", 231 | "Hippocratic-2.1", 232 | "HPND", 233 | "HPND-export-US", 234 | "HPND-sell-variant", 235 | "HTMLTIDY", 236 | "IBM-pibs", 237 | "ICU", 238 | "IJG", 239 | "IJG-short", 240 | "ImageMagick", 241 | "iMatix", 242 | "Imlib2", 243 | "Info-ZIP", 244 | "Intel", 245 | "Intel-ACPI", 246 | "Interbase-1.0", 247 | "IPA", 248 | "IPL-1.0", 249 | "ISC", 250 | "Jam", 251 | "JasPer-2.0", 252 | "JPNIC", 253 | "JSON", 254 | "Knuth-CTAN", 255 | "LAL-1.2", 256 | "LAL-1.3", 257 | "Latex2e", 258 | "Leptonica", 259 | "LGPL-2.0-only", 260 | "LGPL-2.0-or-later", 261 | "LGPL-2.1-only", 262 | "LGPL-2.1-or-later", 263 | "LGPL-3.0-only", 264 | "LGPL-3.0-or-later", 265 | "LGPLLR", 266 | "Libpng", 267 | "libpng-2.0", 268 | "libselinux-1.0", 269 | "libtiff", 270 | "libutil-David-Nugent", 271 | "LiLiQ-P-1.1", 272 | "LiLiQ-R-1.1", 273 | "LiLiQ-Rplus-1.1", 274 | "Linux-man-pages-copyleft", 275 | "Linux-OpenIB", 276 | "LOOP", 277 | "LPL-1.0", 278 | "LPL-1.02", 279 | "LPPL-1.0", 280 | "LPPL-1.1", 281 | "LPPL-1.2", 282 | "LPPL-1.3a", 283 | "LPPL-1.3c", 284 | "LZMA-SDK-9.11-to-9.20", 285 | "LZMA-SDK-9.22", 286 | "MakeIndex", 287 | "Minpack", 288 | "MirOS", 289 | "MIT", 290 | "MIT-0", 291 | "MIT-advertising", 292 | "MIT-CMU", 293 | "MIT-enna", 294 | "MIT-feh", 295 | "MIT-Modern-Variant", 296 | "MIT-open-group", 297 | "MIT-Wu", 298 | "MITNFA", 299 | "Motosoto", 300 | "mpi-permissive", 301 | "mpich2", 302 | "MPL-1.0", 303 | "MPL-1.1", 304 | "MPL-2.0", 305 | "MPL-2.0-no-copyleft-exception", 306 | "mplus", 307 | "MS-LPL", 308 | "MS-PL", 309 | "MS-RL", 310 | "MTLL", 311 | "MulanPSL-1.0", 312 | "MulanPSL-2.0", 313 | "Multics", 314 | "Mup", 315 | "NAIST-2003", 316 | "NASA-1.3", 317 | "Naumen", 318 | "NBPL-1.0", 319 | "NCGL-UK-2.0", 320 | "NCSA", 321 | "Net-SNMP", 322 | "NetCDF", 323 | "Newsletr", 324 | "NGPL", 325 | "NICTA-1.0", 326 | "NIST-PD", 327 | "NIST-PD-fallback", 328 | "NLOD-1.0", 329 | "NLOD-2.0", 330 | "NLPL", 331 | "Nokia", 332 | "NOSL", 333 | "Noweb", 334 | "NPL-1.0", 335 | "NPL-1.1", 336 | "NPOSL-3.0", 337 | "NRL", 338 | "NTP", 339 | "NTP-0", 340 | "O-UDA-1.0", 341 | "OCCT-PL", 342 | "OCLC-2.0", 343 | "ODbL-1.0", 344 | "ODC-By-1.0", 345 | "OFL-1.0", 346 | "OFL-1.0-no-RFN", 347 | "OFL-1.0-RFN", 348 | "OFL-1.1", 349 | "OFL-1.1-no-RFN", 350 | "OFL-1.1-RFN", 351 | "OGC-1.0", 352 | "OGDL-Taiwan-1.0", 353 | "OGL-Canada-2.0", 354 | "OGL-UK-1.0", 355 | "OGL-UK-2.0", 356 | "OGL-UK-3.0", 357 | "OGTSL", 358 | "OLDAP-1.1", 359 | "OLDAP-1.2", 360 | "OLDAP-1.3", 361 | "OLDAP-1.4", 362 | "OLDAP-2.0", 363 | "OLDAP-2.0.1", 364 | "OLDAP-2.1", 365 | "OLDAP-2.2", 366 | "OLDAP-2.2.1", 367 | "OLDAP-2.2.2", 368 | "OLDAP-2.3", 369 | "OLDAP-2.4", 370 | "OLDAP-2.5", 371 | "OLDAP-2.6", 372 | "OLDAP-2.7", 373 | "OLDAP-2.8", 374 | "OML", 375 | "OpenSSL", 376 | "OPL-1.0", 377 | "OPUBL-1.0", 378 | "OSET-PL-2.1", 379 | "OSL-1.0", 380 | "OSL-1.1", 381 | "OSL-2.0", 382 | "OSL-2.1", 383 | "OSL-3.0", 384 | "Parity-6.0.0", 385 | "Parity-7.0.0", 386 | "PDDL-1.0", 387 | "PHP-3.0", 388 | "PHP-3.01", 389 | "Plexus", 390 | "PolyForm-Noncommercial-1.0.0", 391 | "PolyForm-Small-Business-1.0.0", 392 | "PostgreSQL", 393 | "PSF-2.0", 394 | "psfrag", 395 | "psutils", 396 | "Python-2.0", 397 | "Python-2.0.1", 398 | "Qhull", 399 | "QPL-1.0", 400 | "Rdisc", 401 | "RHeCos-1.1", 402 | "RPL-1.1", 403 | "RPL-1.5", 404 | "RPSL-1.0", 405 | "RSA-MD", 406 | "RSCPL", 407 | "Ruby", 408 | "SAX-PD", 409 | "Saxpath", 410 | "SCEA", 411 | "SchemeReport", 412 | "Sendmail", 413 | "Sendmail-8.23", 414 | "SGI-B-1.0", 415 | "SGI-B-1.1", 416 | "SGI-B-2.0", 417 | "SHL-0.5", 418 | "SHL-0.51", 419 | "SimPL-2.0", 420 | "SISSL", 421 | "SISSL-1.2", 422 | "Sleepycat", 423 | "SMLNJ", 424 | "SMPPL", 425 | "SNIA", 426 | "Spencer-86", 427 | "Spencer-94", 428 | "Spencer-99", 429 | "SPL-1.0", 430 | "SSH-OpenSSH", 431 | "SSH-short", 432 | "SSPL-1.0", 433 | "SugarCRM-1.1.3", 434 | "SWL", 435 | "Symlinks", 436 | "TAPR-OHL-1.0", 437 | "TCL", 438 | "TCP-wrappers", 439 | "TMate", 440 | "TORQUE-1.1", 441 | "TOSL", 442 | "TPDL", 443 | "TTWL", 444 | "TU-Berlin-1.0", 445 | "TU-Berlin-2.0", 446 | "UCL-1.0", 447 | "Unicode-DFS-2015", 448 | "Unicode-DFS-2016", 449 | "Unicode-TOU", 450 | "Unlicense", 451 | "UPL-1.0", 452 | "Vim", 453 | "VOSTROM", 454 | "VSL-1.0", 455 | "W3C", 456 | "W3C-19980720", 457 | "W3C-20150513", 458 | "Watcom-1.0", 459 | "Wsuipa", 460 | "WTFPL", 461 | "X11", 462 | "X11-distribute-modifications-variant", 463 | "Xerox", 464 | "XFree86-1.1", 465 | "xinetd", 466 | "Xnet", 467 | "xpp", 468 | "XSkat", 469 | "YPL-1.0", 470 | "YPL-1.1", 471 | "Zed", 472 | "Zend-2.0", 473 | "Zimbra-1.3", 474 | "Zimbra-1.4", 475 | "Zlib", 476 | "zlib-acknowledgement", 477 | "ZPL-1.1", 478 | "ZPL-2.0", 479 | "ZPL-2.1", 480 | -------------------------------------------------------------------------------- /spdxexp/satisfies.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | ) 7 | 8 | // ValidateLicenses checks if given licenses are valid according to spdx. 9 | // Returns true if all licenses are valid; otherwise, false. 10 | // Returns all the invalid licenses contained in the `licenses` argument. 11 | func ValidateLicenses(licenses []string) (bool, []string) { 12 | valid := true 13 | invalidLicenses := []string{} 14 | for _, license := range licenses { 15 | if _, err := parse(license); err != nil { 16 | valid = false 17 | invalidLicenses = append(invalidLicenses, license) 18 | } 19 | } 20 | return valid, invalidLicenses 21 | } 22 | 23 | // Satisfies determines if the allowed list of licenses satisfies the test license expression. 24 | // Returns true if allowed list satisfies test license expression; otherwise, false. 25 | // Returns error if error occurs during processing. 26 | func Satisfies(testExpression string, allowedList []string) (bool, error) { 27 | expressionNode, err := parse(testExpression) 28 | if err != nil { 29 | return false, err 30 | } 31 | if len(allowedList) == 0 { 32 | return false, errors.New("allowedList requires at least one element, but is empty") 33 | } 34 | allowedNodes, err := stringsToNodes(allowedList) 35 | if err != nil { 36 | return false, err 37 | } 38 | sortAndDedup(allowedNodes) 39 | 40 | expandedExpression := expressionNode.expand(true) 41 | 42 | for _, expressionPart := range expandedExpression { 43 | if isCompatible(expressionPart, allowedNodes) { 44 | // return once any expressionPart is compatible with the allow list 45 | // * each part is an array of licenses that are ANDed, meaning all have to be on the allowedList 46 | // * the parts are ORed, meaning only one of the parts need to be compatible 47 | return true, nil 48 | } 49 | } 50 | return false, nil 51 | } 52 | 53 | // stringsToNodes converts an array of single license strings to to an array of license nodes. 54 | func stringsToNodes(licenseStrings []string) ([]*node, error) { 55 | nodes := make([]*node, len(licenseStrings)) 56 | for i, s := range licenseStrings { 57 | node, err := parse(s) 58 | if err != nil { 59 | return nil, err 60 | } 61 | if node.isExpression() { 62 | return nil, errors.New("expressions are not supported in the allowedList") 63 | } 64 | nodes[i] = node 65 | } 66 | return nodes, nil 67 | } 68 | 69 | // isCompatible checks if expressionPart is compatible with allowed list. 70 | // Expression part is an array of licenses that are ANDed together. 71 | // Allowed is an array of licenses that can fulfill the expression. 72 | func isCompatible(expressionPart, allowed []*node) bool { 73 | for _, expLicense := range expressionPart { 74 | compatible := false 75 | for _, allowedLicense := range allowed { 76 | nodes := &nodePair{firstNode: expLicense, secondNode: allowedLicense} 77 | if nodes.licensesAreCompatible() || nodes.licenseRefsAreCompatible() { 78 | compatible = true 79 | break 80 | } 81 | } 82 | if !compatible { 83 | // no compatible license found for one of the required licenses 84 | return false 85 | } 86 | } 87 | // found a compatible license in test for each required license 88 | return true 89 | } 90 | 91 | // expand will expand the given expression into an equivalent array representing ANDed licenses 92 | // grouped in an array and ORed licenses each in a separate array. 93 | // 94 | // Example: 95 | // 96 | // License node: "MIT" becomes [["MIT"]] 97 | // OR Expression: "MIT OR Apache-2.0" becomes [["MIT"], ["Apache-2.0"]] 98 | // AND Expression: "MIT AND Apache-2.0" becomes [["MIT", "Apache-2.0"]] 99 | // OR-AND Expression: "MIT OR Apache-2.0 AND GPL-2.0" becomes [["MIT"], ["Apache-2.0", "GPL-2.0"]] 100 | // OR(AND) Expression: "MIT OR (Apache-2.0 AND GPL-2.0)" becomes [["MIT"], ["Apache-2.0", "GPL-2.0"]] 101 | // AND-OR Expression: "MIT AND Apache-2.0 OR GPL-2.0" becomes [["Apache-2.0", "MIT], ["GPL-2.0"]] 102 | // AND(OR) Expression: "MIT AND (Apache-2.0 OR GPL-2.0)" becomes [["Apache-2.0", "MIT], ["GPL-2.0", "MIT"]] 103 | // OR-AND-OR Expression: "MIT OR ISC AND Apache-2.0 OR GPL-2.0" becomes 104 | // [["MIT"], ["Apache-2.0", "ISC"], ["GPL-2.0"]] 105 | // (OR)AND(OR) Expression: "(MIT OR ISC) AND (Apache-2.0 OR GPL-2.0)" becomes 106 | // [["Apache-2.0", "MIT"], ["GPL-2.0", "MIT"], ["Apache-2.0", "ISC"], ["GPL-2.0", "ISC"]] 107 | // OR(AND)OR Expression: "MIT OR (ISC AND Apache-2.0) OR GPL-2.0" becomes 108 | // [["MIT"], ["Apache-2.0", "ISC"], ["GPL-2.0"]] 109 | // AND-OR-AND Expression: "MIT AND ISC OR Apache-2.0 AND GPL-2.0" becomes 110 | // [["ISC", "MIT"], ["Apache-2.0", "GPL-2.0"]] 111 | // (AND)OR(AND) Expression: "(MIT AND ISC) OR (Apache-2.0 AND GPL-2.0)" becomes 112 | // [["ISC", "MIT"], ["Apache-2.0", "GPL-2.0"]] 113 | // AND(OR)AND Expression: "MIT AND (ISC OR Apache-2.0) AND GPL-2.0" becomes 114 | // [["GPL-2.0", "ISC", "MIT"], ["Apache-2.0", "GPL-2.0", "MIT"]] 115 | func (n *node) expand(withDeepSort bool) [][]*node { 116 | if n.isLicense() || n.isLicenseRef() { 117 | return [][]*node{{n}} 118 | } 119 | 120 | var expanded [][]*node 121 | if n.isOrExpression() { 122 | expanded = n.expandOr() 123 | } else { 124 | expanded = n.expandAnd() 125 | } 126 | 127 | if withDeepSort { 128 | expanded = deepSort(expanded) 129 | } 130 | return expanded 131 | } 132 | 133 | // expandOr expands the given expression into an equivalent array representing ORed licenses each in a separate array. 134 | // 135 | // Example: 136 | // 137 | // OR Expression: "MIT OR Apache-2.0" becomes [["MIT"], ["Apache-2.0"]] 138 | func (n *node) expandOr() [][]*node { 139 | var result [][]*node 140 | result = expandOrTerm(n.left(), result) 141 | result = expandOrTerm(n.right(), result) 142 | return result 143 | } 144 | 145 | // expandOrTerm expands the terms of an OR expression. 146 | func expandOrTerm(term *node, result [][]*node) [][]*node { 147 | if term.isLicense() { 148 | result = append(result, []*node{term}) 149 | } else if term.isExpression() { 150 | if term.isOrExpression() { 151 | left := term.expandOr() 152 | result = append(result, left...) 153 | } else if term.isAndExpression() { 154 | left := term.expandAnd()[0] 155 | result = append(result, left) 156 | } 157 | } 158 | return result 159 | } 160 | 161 | // expandAnd expands the given expression into an equivalent array representing ANDed licenses 162 | // grouped in an array. When an ORed expression is combined with AND, the ORed 163 | // expressions are combined with the ANDed expressions. 164 | // 165 | // Example: 166 | // 167 | // AND Expression: "MIT AND Apache-2.0" becomes [["MIT", "Apache-2.0"]] 168 | // AND(OR) Expression: "MIT AND (Apache-2.0 OR GPL-2.0)" becomes [["Apache-2.0", "MIT], ["GPL-2.0", "MIT"]] 169 | // 170 | // See more examples under func expand. 171 | func (n *node) expandAnd() [][]*node { 172 | left := expandAndTerm(n.left()) 173 | right := expandAndTerm(n.right()) 174 | 175 | if len(left) > 1 || len(right) > 1 { 176 | // an OR expression has been processed 177 | // somewhere on the left and/or right node path 178 | return appendTerms(left, right) 179 | } 180 | 181 | // only AND expressions have been processed 182 | return mergeTerms(left, right) 183 | } 184 | 185 | // expandAndTerm expands the terms of an AND expression. 186 | func expandAndTerm(term *node) [][]*node { 187 | var result [][]*node 188 | if term.isLicense() || term.isLicenseRef() { 189 | result = append(result, []*node{term}) 190 | } else if term.isExpression() { 191 | if term.isAndExpression() { 192 | result = term.expandAnd() 193 | } else if term.isOrExpression() { 194 | result = term.expandOr() 195 | } 196 | } 197 | return result 198 | } 199 | 200 | // appendTerms appends results from expanding the right expression into the results 201 | // from expanding the left expression. When at least one of the left/right 202 | // nodes includes an OR expression, the values are spread across at times 203 | // producing more results than exists in the left or right results. 204 | // 205 | // Example: 206 | // 207 | // left: {{"MIT"}} right: {{"ISC"}, {"Apache-2.0"}} becomes 208 | // {{"MIT", "ISC"}, {"MIT", "Apache-2.0"}} 209 | func appendTerms(left, right [][]*node) [][]*node { 210 | var result [][]*node 211 | for _, r := range right { 212 | for _, l := range left { 213 | tmp := l 214 | tmp = append(tmp, r...) 215 | result = append(result, tmp) 216 | } 217 | } 218 | return result 219 | } 220 | 221 | // mergeTerms merges results from expanding left and right expressions. 222 | // When neither left/right nodes includes an OR expression, the values 223 | // are merged left and right results. 224 | // 225 | // Example: 226 | // 227 | // left: {{"MIT"}} right: {{"ISC", "Apache-2.0"}} becomes 228 | // {{"MIT", "ISC", "Apache-2.0"}} 229 | func mergeTerms(left, right [][]*node) [][]*node { 230 | results := left 231 | for _, r := range right { 232 | for j, l := range results { 233 | results[j] = append(l, r...) 234 | } 235 | } 236 | return results 237 | } 238 | 239 | // sortAndDedup sorts an array of license nodes and then removes duplicates. 240 | func sortAndDedup(nodes []*node) []*node { 241 | if len(nodes) <= 1 { 242 | return nodes 243 | } 244 | 245 | sortLicenses(nodes) 246 | prev := 1 247 | for curr := 1; curr < len(nodes); curr++ { 248 | if *nodes[curr-1].reconstructedLicenseString() != *nodes[curr].reconstructedLicenseString() { 249 | nodes[prev] = nodes[curr] 250 | prev++ 251 | } 252 | } 253 | 254 | return nodes[:prev] 255 | } 256 | 257 | // deepSort sorts a two-dimensional array of license nodes. Internal arrays are sorted first. 258 | // Then each array of nodes are sorted relative to the other arrays. 259 | // 260 | // Example: 261 | // 262 | // BEFORE {{"MIT", "GPL-2.0"}, {"ISC", "Apache-2.0"}} 263 | // AFTER {{"Apache-2.0", "ISC"}, {"GPL-2.0", "MIT"}} 264 | func deepSort(nodes2d [][]*node) [][]*node { 265 | if len(nodes2d) == 0 || len(nodes2d) == 1 && len(nodes2d[0]) <= 1 { 266 | return nodes2d 267 | } 268 | 269 | // sort each array internally 270 | // Example: 271 | // BEFORE {{"MIT", "GPL-2.0"}, {"ISC", "Apache-2.0"}} 272 | // AFTER {{"GPL-2.0", "MIT"}, {"Apache-2.0", "ISC"}} 273 | for _, nodes := range nodes2d { 274 | if len(nodes) > 1 { 275 | sortLicenses(nodes) 276 | } 277 | } 278 | 279 | // sort arrays relative to each other 280 | // Example: 281 | // BEFORE {{"GPL-2.0", "MIT"}, {"Apache-2.0", "ISC"}} 282 | // AFTER {{"Apache-2.0", "ISC"}, {"GPL-2.0", "MIT"}} 283 | sort.Slice(nodes2d, func(i, j int) bool { 284 | // TODO: Consider refactor to map nodes to licenseString before processing. 285 | for k := range nodes2d[j] { 286 | if k >= len(nodes2d[i]) { 287 | // if the first k elements are equal and the second array is 288 | // longer than the first, the first is considered less than 289 | return true 290 | } 291 | iLicense := *nodes2d[i][k].reconstructedLicenseString() 292 | jLicense := *nodes2d[j][k].reconstructedLicenseString() 293 | if iLicense != jLicense { 294 | // when elements are not equal, return true if first is less than 295 | return iLicense < jLicense 296 | } 297 | } 298 | // all elements are equal, return false to avoid a swap 299 | return false 300 | }) 301 | 302 | return nodes2d 303 | } 304 | -------------------------------------------------------------------------------- /spdxexp/parse.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // The ABNF grammar in the spec is totally ambiguous. 9 | // 10 | // This parser follows the operator precedence defined in the 11 | // `Order of Precedence and Parentheses` section. 12 | 13 | type tokenStream struct { 14 | tokens []token 15 | index int 16 | err error 17 | } 18 | 19 | func parse(source string) (*node, error) { 20 | if len(source) == 0 { 21 | return nil, errors.New("parse error - cannot parse empty string") 22 | } 23 | tokens, err := scan(source) 24 | if err != nil { 25 | return nil, err 26 | } 27 | tokns := &tokenStream{tokens: tokens, index: 0, err: nil} 28 | return tokns.parseTokens(), tokns.err 29 | } 30 | 31 | func (t *tokenStream) parseTokens() *node { 32 | if len(t.tokens) == 0 { 33 | // malformed with no tokens 34 | t.err = errors.New("no tokens to parse") 35 | return nil 36 | } 37 | 38 | node := t.parseExpression() 39 | if t.err != nil { 40 | return nil 41 | } 42 | 43 | if node == nil { 44 | // unable to parse expression for unknown reason 45 | t.err = errors.New("syntax error") 46 | return nil 47 | } else if t.hasMore() { 48 | // malformed with too many tokens - try to determine the cause 49 | 50 | // check for close parenthesis without matching open parenthesis 51 | closeParen := t.parseOperator(")") 52 | if closeParen != nil { 53 | t.err = errors.New("close parenthesis does not have a matching open parenthesis") 54 | return nil 55 | } 56 | 57 | // check for licenses without operator 58 | lic := t.parseLicense() 59 | if lic != nil { 60 | t.err = errors.New("licenses or expressions are not separated by an operator") 61 | return nil 62 | } 63 | 64 | // cannot determine what syntax error occurred 65 | t.err = errors.New("syntax error") 66 | return nil 67 | } 68 | 69 | // all is well 70 | return node 71 | } 72 | 73 | // Return true if there is another token to process; otherwise, return false. 74 | func (t *tokenStream) hasMore() bool { 75 | return t.index < len(t.tokens) 76 | } 77 | 78 | // Return the value of the next token without advancing the index. 79 | func (t *tokenStream) peek() *token { 80 | if t.hasMore() { 81 | token := t.tokens[t.index] 82 | return &token 83 | } 84 | return nil 85 | } 86 | 87 | // Advance the index to the next token. 88 | func (t *tokenStream) next() { 89 | if !t.hasMore() { 90 | t.err = errors.New("read past end of tokens") 91 | return 92 | } 93 | t.index++ 94 | } 95 | 96 | func (t *tokenStream) parseParenthesizedExpression() *node { 97 | openParen := t.parseOperator("(") 98 | if openParen == nil { 99 | // paren not found 100 | return nil 101 | } 102 | 103 | expr := t.parseExpression() 104 | if t.err != nil { 105 | return nil 106 | } 107 | 108 | if !t.hasMore() { 109 | // no more tokens, so missing closing paren 110 | t.err = errors.New("open parenthesis does not have a matching close parenthesis") 111 | return nil 112 | } 113 | 114 | closeParen := t.parseOperator(")") 115 | if closeParen == nil { 116 | t.err = errors.New("open parenthesis does not have a matching close parenthesis") 117 | return nil 118 | } 119 | 120 | return expr 121 | } 122 | 123 | func (t *tokenStream) parseAtom() *node { 124 | parenNode := t.parseParenthesizedExpression() 125 | if t.err != nil { 126 | return nil 127 | } 128 | if parenNode != nil { 129 | return parenNode 130 | } 131 | 132 | refNode := t.parseLicenseRef() 133 | if t.err != nil { 134 | return nil 135 | } 136 | if refNode != nil { 137 | return refNode 138 | } 139 | 140 | licenseNode := t.parseLicense() 141 | if t.err != nil { 142 | return nil 143 | } 144 | if licenseNode != nil { 145 | return licenseNode 146 | } 147 | 148 | // no atom found - try to determine the cause 149 | if t.hasMore() { 150 | // check for operators 151 | operator := t.parseOperator(")") 152 | if operator != nil { 153 | if t.index == 1 { 154 | t.err = errors.New("expression starts with close parenthesis") 155 | } else { 156 | t.err = errors.New("expected license or expression, but found close parenthesis") 157 | } 158 | return nil 159 | } 160 | 161 | operator = t.parseOperator("OR") 162 | if operator != nil { 163 | if t.index == 1 { 164 | t.err = errors.New("expression starts with OR") 165 | } else { 166 | t.err = errors.New("expected license or expression, but found OR") 167 | } 168 | return nil 169 | } 170 | 171 | operator = t.parseOperator("AND") 172 | if operator != nil { 173 | if t.index == 1 { 174 | t.err = errors.New("expression starts with AND") 175 | } else { 176 | t.err = errors.New("expected license or expression, but found AND") 177 | } 178 | return nil 179 | } 180 | 181 | // cannot determine what syntax error occurred 182 | t.err = errors.New("syntax error") 183 | return nil 184 | } 185 | 186 | t.err = errors.New("expected node, but found none") 187 | return nil 188 | } 189 | 190 | func (t *tokenStream) parseExpression() *node { 191 | left := t.parseAnd() 192 | if t.err != nil { 193 | return nil 194 | } 195 | if left == nil { 196 | return nil 197 | } 198 | if !t.hasMore() { 199 | // expression found and no more tokens to process 200 | return left 201 | } 202 | 203 | operator := t.parseOperator("OR") 204 | if operator == nil { 205 | return left 206 | } 207 | op := strings.ToLower(*operator) 208 | 209 | if !t.hasMore() { 210 | // expression found and no more tokens to process 211 | t.err = errors.New("expected expression following OR, but found none") 212 | return nil 213 | } 214 | 215 | right := t.parseExpression() 216 | if t.err != nil { 217 | return nil 218 | } 219 | if right == nil { 220 | t.err = errors.New("expected expression following OR, but found none") 221 | return nil 222 | } 223 | 224 | return &(node{ 225 | role: expressionNode, 226 | exp: &(expressionNodePartial{ 227 | left: left, 228 | conjunction: op, 229 | right: right, 230 | }), 231 | }) 232 | } 233 | 234 | // Return a node representation of an atomic value or an AND expression. If a malformed 235 | // atomic value or expression is found, an error is returned. Advances the index if a 236 | // valid atomic value or a valid expression is found. 237 | func (t *tokenStream) parseAnd() *node { 238 | left := t.parseAtom() 239 | if t.err != nil { 240 | return nil 241 | } 242 | if left == nil { 243 | return nil 244 | } 245 | if !t.hasMore() { 246 | // atomic token found and no more tokens to process 247 | return left 248 | } 249 | 250 | operator := t.parseOperator("AND") 251 | if operator == nil { 252 | return left 253 | } 254 | 255 | if !t.hasMore() { 256 | // expression found and no more tokens to process 257 | t.err = errors.New("expected expression following AND, but found none") 258 | return nil 259 | } 260 | 261 | right := t.parseAnd() 262 | if t.err != nil { 263 | return nil 264 | } 265 | if right == nil { 266 | t.err = errors.New("expected expression following AND, but found none") 267 | return nil 268 | } 269 | 270 | exp := expressionNodePartial{left: left, conjunction: "and", right: right} 271 | 272 | return &(node{ 273 | role: expressionNode, 274 | exp: &exp, 275 | }) 276 | } 277 | 278 | // Return a node representation of a License Reference. If a malformed license reference is 279 | // found, an error is returned. Advances the index if a valid license reference is found. 280 | func (t *tokenStream) parseLicenseRef() *node { 281 | ref := referenceNodePartial{documentRef: "", hasDocumentRef: false, licenseRef: ""} 282 | 283 | token := t.peek() 284 | if token.role == documentRefToken { 285 | ref.documentRef = token.value 286 | ref.hasDocumentRef = true 287 | t.next() 288 | 289 | operator := t.parseOperator(":") 290 | if operator == nil { 291 | t.err = errors.New("expected ':' after 'DocumentRef-...'") 292 | return nil 293 | } 294 | } 295 | 296 | token = t.peek() 297 | if token.role != licenseRefToken && ref.hasDocumentRef { 298 | t.err = errors.New("expected 'LicenseRef-...' after 'DocumentRef-...'") 299 | return nil 300 | } else if token.role != licenseRefToken { 301 | // not found is not an error as long as DocumentRef and : weren't the previous tokens 302 | return nil 303 | } 304 | 305 | ref.licenseRef = token.value 306 | t.next() 307 | 308 | return &(node{ 309 | role: licenseRefNode, 310 | ref: &ref, 311 | }) 312 | } 313 | 314 | // Return a node representation of a License. If a malformed license is found, 315 | // an error is returned. Advances the index if a valid license is found. 316 | func (t *tokenStream) parseLicense() *node { 317 | token := t.peek() 318 | if token.role != licenseToken { 319 | return nil 320 | } 321 | t.next() 322 | 323 | lic := licenseNodePartial{ 324 | license: token.value, 325 | hasPlus: false, 326 | hasException: false, 327 | exception: ""} 328 | 329 | // for licenses that specifically support -or-later, a `+` operator token isn't expected to be present 330 | if strings.HasSuffix(token.value, "-or-later") { 331 | lic.hasPlus = true 332 | } 333 | 334 | if t.hasMore() { 335 | // use new var idx to avoid creating a new var index 336 | operator := t.parseOperator("+") 337 | if operator != nil { 338 | lic.hasPlus = true 339 | } 340 | 341 | if t.hasMore() { 342 | exception := t.parseWith() 343 | if t.err != nil { 344 | return nil 345 | } 346 | if exception != nil { 347 | lic.hasException = true 348 | lic.exception = *exception 349 | t.next() 350 | } 351 | } 352 | } 353 | 354 | return &(node{ 355 | role: licenseNode, 356 | lic: &lic, 357 | }) 358 | } 359 | 360 | // Return the operator's value (e.g. AND, OR, WITH) if the current token is an OPERATOR. 361 | // Advances the index if the operator is found. 362 | func (t *tokenStream) parseOperator(operator string) *string { 363 | token := t.peek() 364 | if token.role == operatorToken && token.value == operator { 365 | t.next() 366 | return &(token.value) 367 | } 368 | // requested operator not found 369 | return nil 370 | } 371 | 372 | // Get the exception license when the WITH operator is found. 373 | // Return without advancing the index if the current token is not the WITH operator. 374 | // Raise an error if the WITH operator is not followed by and EXCEPTION license. 375 | func (t *tokenStream) parseWith() *string { 376 | operator := t.parseOperator("WITH") 377 | if operator == nil { 378 | // WITH not found is not an error 379 | return nil 380 | } 381 | 382 | token := t.peek() 383 | if token.role != exceptionToken { 384 | t.err = errors.New("expected exception after 'WITH'") 385 | return nil 386 | } 387 | 388 | return &(token.value) 389 | } 390 | 391 | // Returns a human readable representation of the node tree. 392 | func (n *node) string() string { 393 | switch n.role { 394 | case expressionNode: 395 | return expressionString(*n.exp) 396 | case licenseNode: 397 | return licenseString(*n.lic) 398 | case licenseRefNode: 399 | return referenceString(*n.ref) 400 | } 401 | return "" 402 | } 403 | 404 | func expressionString(exp expressionNodePartial) string { 405 | s := "{ LEFT: " + exp.left.string() + " " 406 | s += exp.conjunction 407 | s += " RIGHT: " + exp.right.string() + " }" 408 | return s 409 | } 410 | 411 | func licenseString(lic licenseNodePartial) string { 412 | s := lic.license 413 | if lic.hasPlus { 414 | s += "+" 415 | } 416 | if lic.hasException { 417 | s += " with " + lic.exception 418 | } 419 | return s 420 | } 421 | 422 | func referenceString(ref referenceNodePartial) string { 423 | s := "" 424 | if ref.hasDocumentRef { 425 | s = "DocumentRef-" + ref.documentRef + ":" 426 | } 427 | s += "LicenseRef-" + ref.licenseRef 428 | return s 429 | } 430 | -------------------------------------------------------------------------------- /spdxexp/scan_test.go: -------------------------------------------------------------------------------- 1 | package spdxexp 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestScan(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | expression string 15 | tokens []token 16 | err error 17 | }{ 18 | {"single license", "MIT", 19 | []token{ 20 | {role: licenseToken, value: "MIT"}, 21 | }, nil}, 22 | {"single license - diff case", "mit", 23 | []token{ 24 | {role: licenseToken, value: "MIT"}, 25 | }, nil}, 26 | {"empty expression", "", []token(nil), nil}, 27 | {"invalid license", "NON-EXISTENT-LICENSE", []token(nil), 28 | errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, 29 | {"two licenses using AND", "MIT AND Apache-2.0", 30 | []token{ 31 | {role: licenseToken, value: "MIT"}, 32 | {role: operatorToken, value: "AND"}, 33 | {role: licenseToken, value: "Apache-2.0"}, 34 | }, nil}, 35 | {"two licenses using OR inside paren", "(MIT OR Apache-2.0)", 36 | []token{ 37 | {role: operatorToken, value: "("}, 38 | {role: licenseToken, value: "MIT"}, 39 | {role: operatorToken, value: "OR"}, 40 | {role: licenseToken, value: "Apache-2.0"}, 41 | {role: operatorToken, value: ")"}, 42 | }, nil}, 43 | {"kitchen sink", " (MIT AND Apache-1.0+) OR DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2 OR (GPL-2.0 WITH Bison-exception-2.2)", 44 | []token{ 45 | {role: operatorToken, value: "("}, 46 | {role: licenseToken, value: "MIT"}, 47 | {role: operatorToken, value: "AND"}, 48 | {role: licenseToken, value: "Apache-1.0"}, 49 | {role: operatorToken, value: "+"}, 50 | {role: operatorToken, value: ")"}, 51 | {role: operatorToken, value: "OR"}, 52 | {role: documentRefToken, value: "spdx-tool-1.2"}, 53 | {role: operatorToken, value: ":"}, 54 | {role: licenseRefToken, value: "MIT-Style-2"}, 55 | {role: operatorToken, value: "OR"}, 56 | {role: operatorToken, value: "("}, 57 | {role: licenseToken, value: "GPL-2.0"}, 58 | {role: operatorToken, value: "WITH"}, 59 | {role: exceptionToken, value: "Bison-exception-2.2"}, 60 | {role: operatorToken, value: ")"}, 61 | }, nil}, 62 | } 63 | 64 | for _, test := range tests { 65 | test := test 66 | t.Run(test.name, func(t *testing.T) { 67 | tokens, err := scan(test.expression) 68 | 69 | require.Equal(t, test.err, err) 70 | assert.Equal(t, test.tokens, tokens) 71 | }) 72 | } 73 | } 74 | 75 | func TestHasMoreSource(t *testing.T) { 76 | tests := []struct { 77 | name string 78 | exp *expressionStream 79 | result bool 80 | }{ 81 | {"at start", getExpressionStream("MIT OR Apache-2.0", 0), true}, 82 | {"at middle", getExpressionStream("MIT OR Apache-2.0", 3), true}, 83 | {"at end", getExpressionStream("MIT OR Apache-2.0", len("MIT OR Apache-2.0")), false}, 84 | {"past end", getExpressionStream("MIT OR Apache-2.0", 50), false}, 85 | } 86 | 87 | for _, test := range tests { 88 | test := test 89 | t.Run(test.name, func(t *testing.T) { 90 | assert.Equal(t, test.result, test.exp.hasMore()) 91 | }) 92 | } 93 | } 94 | 95 | func TestParseToken(t *testing.T) { 96 | tests := []struct { 97 | name string 98 | exp *expressionStream 99 | token *token 100 | newIndex int 101 | err error 102 | }{ 103 | {"operator found", getExpressionStream("MIT AND Apache-2.0", 4), 104 | &token{role: operatorToken, value: "AND"}, 7, nil}, 105 | {"operator error", getExpressionStream("Apache-1.0 + OR MIT", 11), 106 | nil, 11, errors.New("unexpected space before +")}, 107 | {"document ref found", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 0), 108 | &token{role: documentRefToken, value: "spdx-tool-1.2"}, 25, nil}, 109 | {"document ref error", getExpressionStream("DocumentRef-!23", 0), 110 | nil, 12, errors.New("expected id at offset 12")}, 111 | {"license ref found", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 26), 112 | &token{role: licenseRefToken, value: "MIT-Style-2"}, 48, nil}, 113 | {"license ref error", getExpressionStream("LicenseRef-!23", 0), 114 | nil, 11, errors.New("expected id at offset 11")}, 115 | {"identifier found", getExpressionStream("MIT AND Apache-2.0", 8), 116 | &token{role: licenseToken, value: "Apache-2.0"}, 18, nil}, 117 | {"identifier error", getExpressionStream("NON-EXISTENT-LICENSE", 0), 118 | nil, 0, errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, 119 | } 120 | 121 | for _, test := range tests { 122 | test := test 123 | t.Run(test.name, func(t *testing.T) { 124 | tokn := test.exp.parseToken() 125 | assert.Equal(t, test.newIndex, test.exp.index) 126 | 127 | require.Equal(t, test.err, test.exp.err) 128 | if test.err != nil { 129 | // token is nil when error occurs or token is not recognized 130 | var nilToken *token 131 | assert.Equal(t, nilToken, tokn) 132 | return 133 | } 134 | 135 | // token recognized, check token value 136 | assert.Equal(t, test.token, tokn) 137 | }) 138 | } 139 | } 140 | 141 | func TestReadRegex(t *testing.T) { 142 | tests := []struct { 143 | name string 144 | exp *expressionStream 145 | pattern string 146 | match string 147 | newIndex int 148 | }{ 149 | {"regex to skip leading blank in middle", getExpressionStream("MIT OR Apache-2.0", 3), 150 | "[ ]*", " ", 4}, 151 | {"regex for id", getExpressionStream("LicenseRef-MIT-Style-1", 11), 152 | "[A-Za-z0-9-.]+", "MIT-Style-1", 22}, 153 | } 154 | 155 | for _, test := range tests { 156 | test := test 157 | t.Run(test.name, func(t *testing.T) { 158 | match := test.exp.readRegex(test.pattern) 159 | assert.Equal(t, test.match, match) 160 | assert.Equal(t, test.newIndex, test.exp.index) 161 | }) 162 | } 163 | } 164 | 165 | func TestRead(t *testing.T) { 166 | tests := []struct { 167 | name string 168 | exp *expressionStream 169 | next string 170 | match string 171 | newIndex int 172 | }{ 173 | {"at first - match word", getExpressionStream("MIT OR Apache-2.0", 0), "MIT", "MIT", 3}, 174 | {"at middle - match operator", getExpressionStream("MIT OR Apache-2.0", 4), "OR", "OR", 6}, 175 | {"at middle - match last word", getExpressionStream("MIT OR Apache-2.0", 7), "Apache-2.0", "Apache-2.0", 17}, 176 | {"at first - no match", getExpressionStream("MIT OR Apache-2.0", 0), "GPL", "", 0}, 177 | {"at middle - no match for operator", getExpressionStream("MIT OR Apache-2.0", 4), "AND", "", 4}, 178 | {"at middle - no match last word", getExpressionStream("MIT OR Apache-2.0", 7), "GPL", "", 7}, 179 | } 180 | 181 | for _, test := range tests { 182 | test := test 183 | t.Run(test.name, func(t *testing.T) { 184 | match := test.exp.read(test.next) 185 | assert.Equal(t, test.match, match) 186 | assert.Equal(t, test.newIndex, test.exp.index) 187 | }) 188 | } 189 | } 190 | 191 | func TestSkipWhitespace(t *testing.T) { 192 | tests := []struct { 193 | name string 194 | exp *expressionStream 195 | newIndex int 196 | }{ 197 | {"at first - no blanks", getExpressionStream("MIT OR Apache-2.0", 0), 0}, 198 | {"at first - with blanks", getExpressionStream(" MIT OR Apache-2 .0", 0), 2}, 199 | {"at middle - no blanks", getExpressionStream("MIT OR Apache-2.0", 4), 4}, 200 | {"at middle - with blanks", getExpressionStream("MIT OR Apache-2.0", 3), 4}, 201 | {"at end - no blanks", getExpressionStream("MIT OR GPL", 10), 10}, 202 | {"at end - with blanks", getExpressionStream("MIT OR GPL ", 10), 12}, 203 | } 204 | 205 | for _, test := range tests { 206 | test := test 207 | t.Run(test.name, func(t *testing.T) { 208 | test.exp.skipWhitespace() 209 | assert.Equal(t, test.newIndex, test.exp.index) 210 | }) 211 | } 212 | } 213 | 214 | func TestReadOperator(t *testing.T) { 215 | tests := []struct { 216 | name string 217 | exp *expressionStream 218 | operator *token 219 | newIndex int 220 | err error 221 | }{ 222 | {"WITH operator", getExpressionStream("MIT WITH Bison-exception-2.2", 4), 223 | &token{role: operatorToken, value: "WITH"}, 8, nil}, 224 | {"AND operator", getExpressionStream("MIT AND Apache-2.0", 4), 225 | &token{role: operatorToken, value: "AND"}, 7, nil}, 226 | {"OR operator", getExpressionStream("MIT OR Apache-2.0", 4), 227 | &token{role: operatorToken, value: "OR"}, 6, nil}, 228 | {"( operator", getExpressionStream("(MIT OR Apache-2.0)", 0), 229 | &token{role: operatorToken, value: "("}, 1, nil}, 230 | {") operator", getExpressionStream("(MIT OR Apache-2.0)", 18), 231 | &token{role: operatorToken, value: ")"}, 19, nil}, 232 | {": operator", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 25), 233 | &token{role: operatorToken, value: ":"}, 26, nil}, 234 | {"plus operator - correctly used", getExpressionStream("Apache-1.0+ OR MIT", 10), 235 | &token{role: operatorToken, value: "+"}, 11, nil}, 236 | {"plus operator - with preceding space", getExpressionStream("Apache-1.0 + OR MIT", 11), 237 | nil, 11, errors.New("unexpected space before +")}, 238 | {"operator not found", getExpressionStream("MIT AND Apache-2.0", 8), 239 | nil, 8, nil}, 240 | } 241 | 242 | for _, test := range tests { 243 | test := test 244 | t.Run(test.name, func(t *testing.T) { 245 | operator := test.exp.readOperator() 246 | assert.Equal(t, test.newIndex, test.exp.index) 247 | require.Equal(t, test.err, test.exp.err) 248 | assert.Equal(t, test.operator, operator) 249 | }) 250 | } 251 | } 252 | 253 | func TestReadId(t *testing.T) { 254 | tests := []struct { 255 | name string 256 | exp *expressionStream 257 | id string 258 | newIndex int 259 | }{ 260 | {"valid numeric id", getExpressionStream("LicenseRef-23", 11), "23", 13}, 261 | {"valid id with dashes", getExpressionStream("LicenseRef-MIT-Style-1", 11), "MIT-Style-1", 22}, 262 | {"valid id with period", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 12), "spdx-tool-1.2", 25}, 263 | {"invalid starts with non-supported character", getExpressionStream("LicenseRef-!23", 11), "", 11}, 264 | {"invalid non-supported character in middle", getExpressionStream("LicenseRef-2!3", 11), "2", 12}, 265 | {"invalid ends with non-supported character", getExpressionStream("LicenseRef-23!", 11), "23", 13}, 266 | } 267 | 268 | for _, test := range tests { 269 | test := test 270 | t.Run(test.name, func(t *testing.T) { 271 | id := test.exp.readID() 272 | // check values if there isn't an error 273 | assert.Equal(t, test.id, id) 274 | assert.Equal(t, test.newIndex, test.exp.index) 275 | }) 276 | } 277 | } 278 | 279 | func TestReadDocumentRef(t *testing.T) { 280 | tests := []struct { 281 | name string 282 | exp *expressionStream 283 | ref *token 284 | newIndex int 285 | err error 286 | }{ 287 | {"valid document ref with id", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 0), &token{role: documentRefToken, value: "spdx-tool-1.2"}, 25, nil}, 288 | {"document ref not found", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 26), nil, 26, nil}, 289 | {"invalid document ref with bad id", getExpressionStream("DocumentRef-!23", 0), nil, 12, errors.New("expected id at offset 12")}, 290 | } 291 | 292 | for _, test := range tests { 293 | test := test 294 | t.Run(test.name, func(t *testing.T) { 295 | ref := test.exp.readDocumentRef() 296 | assert.Equal(t, test.newIndex, test.exp.index) 297 | 298 | require.Equal(t, test.err, test.exp.err) 299 | if test.err != nil { 300 | // ref should be nil when error occurs or a ref is not found 301 | var nilToken *token 302 | assert.Equal(t, nilToken, ref, "Expected nil token when error occurs.") 303 | return 304 | } 305 | 306 | // ref found, check ref value 307 | assert.Equal(t, test.ref, ref) 308 | }) 309 | } 310 | } 311 | 312 | func TestReadLicenseRef(t *testing.T) { 313 | tests := []struct { 314 | name string 315 | exp *expressionStream 316 | ref *token 317 | newIndex int 318 | err error 319 | }{ 320 | {"valid license ref with id", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 26), &token{role: licenseRefToken, value: "MIT-Style-2"}, 48, nil}, 321 | {"license ref not found", getExpressionStream("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", 0), nil, 0, nil}, 322 | {"invalid license ref with bad id", getExpressionStream("LicenseRef-!23", 0), nil, 11, errors.New("expected id at offset 11")}, 323 | } 324 | 325 | for _, test := range tests { 326 | test := test 327 | t.Run(test.name, func(t *testing.T) { 328 | ref := test.exp.readLicenseRef() 329 | assert.Equal(t, test.newIndex, test.exp.index) 330 | 331 | require.Equal(t, test.err, test.exp.err) 332 | if test.err != nil { 333 | // ref should be nil when error occurs or a ref is not found 334 | var nilToken *token 335 | assert.Equal(t, nilToken, ref) 336 | return 337 | } 338 | 339 | // ref found, check ref value 340 | assert.Equal(t, test.ref, ref) 341 | }) 342 | } 343 | } 344 | 345 | func TestReadLicense(t *testing.T) { 346 | tests := []struct { 347 | name string 348 | exp *expressionStream 349 | license *token 350 | newExpression string 351 | newIndex int 352 | err error 353 | }{ 354 | {"active license", getExpressionStream("MIT", 0), &token{role: licenseToken, value: "MIT"}, "MIT", 3, nil}, 355 | {"active -or-later", getExpressionStream("AGPL-1.0-or-later", 0), &token{role: licenseToken, value: "AGPL-1.0-or-later"}, "AGPL-1.0-or-later", 17, nil}, 356 | {"active -or-later using +", getExpressionStream("AGPL-1.0+", 0), &token{role: licenseToken, value: "AGPL-1.0-or-later"}, "AGPL-1.0+", 9, nil}, // no valid example for this; all that include -or-later have the base as a deprecated license 357 | {"active -or-later not in list", getExpressionStream("Apache-1.0-or-later", 0), &token{role: licenseToken, value: "Apache-1.0"}, "Apache-1.0+", 10, nil}, 358 | {"active -only", getExpressionStream("GPL-2.0-only", 0), &token{role: licenseToken, value: "GPL-2.0-only"}, "GPL-2.0-only", 12, nil}, 359 | {"active -only not in list", getExpressionStream("ECL-1.0-only", 0), &token{role: licenseToken, value: "ECL-1.0"}, "ECL-1.0-only", 12, nil}, 360 | {"deprecated license", getExpressionStream("LGPL-2.1", 0), &token{role: licenseToken, value: "LGPL-2.1"}, "LGPL-2.1", 8, nil}, 361 | {"exception license", getExpressionStream("GPL-CC-1.0", 0), &token{role: exceptionToken, value: "GPL-CC-1.0"}, "GPL-CC-1.0", 10, nil}, 362 | {"invalid license", getExpressionStream("NON-EXISTENT-LICENSE", 0), nil, "NON-EXISTENT-LICENSE", 0, errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, 363 | } 364 | 365 | for _, test := range tests { 366 | test := test 367 | t.Run(test.name, func(t *testing.T) { 368 | license := test.exp.readLicense() 369 | assert.Equal(t, test.newIndex, test.exp.index) 370 | 371 | require.Equal(t, test.err, test.exp.err) 372 | if test.err != nil { 373 | // license should be nil when error occurs or a license is not found 374 | var nilToken *token 375 | assert.Equal(t, nilToken, license) 376 | return 377 | } 378 | 379 | // license found, check license value 380 | assert.Equal(t, test.license, license) 381 | assert.Equal(t, test.newExpression, test.exp.expression) 382 | }) 383 | } 384 | } 385 | 386 | func getExpressionStream(expression string, index int) *expressionStream { 387 | return &expressionStream{ 388 | expression: expression, 389 | index: index, 390 | err: nil, 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /spdxexp/spdxlicenses/get_licenses.go: -------------------------------------------------------------------------------- 1 | package spdxlicenses 2 | 3 | // Code generated by go-spdx cmd/license.go. DO NOT EDIT. 4 | // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. 5 | 6 | // GetLicenses returns a slice of active license IDs. 7 | func GetLicenses() []string { 8 | return []string{ 9 | "0BSD", 10 | "3D-Slicer-1.0", 11 | "AAL", 12 | "Abstyles", 13 | "AdaCore-doc", 14 | "Adobe-2006", 15 | "Adobe-Display-PostScript", 16 | "Adobe-Glyph", 17 | "Adobe-Utopia", 18 | "ADSL", 19 | "Advanced-Cryptics-Dictionary", 20 | "AFL-1.1", 21 | "AFL-1.2", 22 | "AFL-2.0", 23 | "AFL-2.1", 24 | "AFL-3.0", 25 | "Afmparse", 26 | "AGPL-1.0-only", 27 | "AGPL-1.0-or-later", 28 | "AGPL-3.0-only", 29 | "AGPL-3.0-or-later", 30 | "Aladdin", 31 | "ALGLIB-Documentation", 32 | "AMD-newlib", 33 | "AMDPLPA", 34 | "AML", 35 | "AML-glslang", 36 | "AMPAS", 37 | "ANTLR-PD", 38 | "ANTLR-PD-fallback", 39 | "any-OSI", 40 | "any-OSI-perl-modules", 41 | "Apache-1.0", 42 | "Apache-1.1", 43 | "Apache-2.0", 44 | "APAFML", 45 | "APL-1.0", 46 | "App-s2p", 47 | "APSL-1.0", 48 | "APSL-1.1", 49 | "APSL-1.2", 50 | "APSL-2.0", 51 | "Arphic-1999", 52 | "Artistic-1.0", 53 | "Artistic-1.0-cl8", 54 | "Artistic-1.0-Perl", 55 | "Artistic-2.0", 56 | "Artistic-dist", 57 | "Aspell-RU", 58 | "ASWF-Digital-Assets-1.0", 59 | "ASWF-Digital-Assets-1.1", 60 | "Baekmuk", 61 | "Bahyph", 62 | "Barr", 63 | "bcrypt-Solar-Designer", 64 | "Beerware", 65 | "Bitstream-Charter", 66 | "Bitstream-Vera", 67 | "BitTorrent-1.0", 68 | "BitTorrent-1.1", 69 | "blessing", 70 | "BlueOak-1.0.0", 71 | "Boehm-GC", 72 | "Boehm-GC-without-fee", 73 | "Borceux", 74 | "Brian-Gladman-2-Clause", 75 | "Brian-Gladman-3-Clause", 76 | "BSD-1-Clause", 77 | "BSD-2-Clause", 78 | "BSD-2-Clause-Darwin", 79 | "BSD-2-Clause-first-lines", 80 | "BSD-2-Clause-Patent", 81 | "BSD-2-Clause-pkgconf-disclaimer", 82 | "BSD-2-Clause-Views", 83 | "BSD-3-Clause", 84 | "BSD-3-Clause-acpica", 85 | "BSD-3-Clause-Attribution", 86 | "BSD-3-Clause-Clear", 87 | "BSD-3-Clause-flex", 88 | "BSD-3-Clause-HP", 89 | "BSD-3-Clause-LBNL", 90 | "BSD-3-Clause-Modification", 91 | "BSD-3-Clause-No-Military-License", 92 | "BSD-3-Clause-No-Nuclear-License", 93 | "BSD-3-Clause-No-Nuclear-License-2014", 94 | "BSD-3-Clause-No-Nuclear-Warranty", 95 | "BSD-3-Clause-Open-MPI", 96 | "BSD-3-Clause-Sun", 97 | "BSD-3-Clause-Tso", 98 | "BSD-4-Clause", 99 | "BSD-4-Clause-Shortened", 100 | "BSD-4-Clause-UC", 101 | "BSD-4.3RENO", 102 | "BSD-4.3TAHOE", 103 | "BSD-Advertising-Acknowledgement", 104 | "BSD-Attribution-HPND-disclaimer", 105 | "BSD-Inferno-Nettverk", 106 | "BSD-Mark-Modifications", 107 | "BSD-Protection", 108 | "BSD-Source-beginning-file", 109 | "BSD-Source-Code", 110 | "BSD-Systemics", 111 | "BSD-Systemics-W3Works", 112 | "BSL-1.0", 113 | "Buddy", 114 | "BUSL-1.1", 115 | "bzip2-1.0.6", 116 | "C-UDA-1.0", 117 | "CAL-1.0", 118 | "CAL-1.0-Combined-Work-Exception", 119 | "Caldera", 120 | "Caldera-no-preamble", 121 | "Catharon", 122 | "CATOSL-1.1", 123 | "CC-BY-1.0", 124 | "CC-BY-2.0", 125 | "CC-BY-2.5", 126 | "CC-BY-2.5-AU", 127 | "CC-BY-3.0", 128 | "CC-BY-3.0-AT", 129 | "CC-BY-3.0-AU", 130 | "CC-BY-3.0-DE", 131 | "CC-BY-3.0-IGO", 132 | "CC-BY-3.0-NL", 133 | "CC-BY-3.0-US", 134 | "CC-BY-4.0", 135 | "CC-BY-NC-1.0", 136 | "CC-BY-NC-2.0", 137 | "CC-BY-NC-2.5", 138 | "CC-BY-NC-3.0", 139 | "CC-BY-NC-3.0-DE", 140 | "CC-BY-NC-4.0", 141 | "CC-BY-NC-ND-1.0", 142 | "CC-BY-NC-ND-2.0", 143 | "CC-BY-NC-ND-2.5", 144 | "CC-BY-NC-ND-3.0", 145 | "CC-BY-NC-ND-3.0-DE", 146 | "CC-BY-NC-ND-3.0-IGO", 147 | "CC-BY-NC-ND-4.0", 148 | "CC-BY-NC-SA-1.0", 149 | "CC-BY-NC-SA-2.0", 150 | "CC-BY-NC-SA-2.0-DE", 151 | "CC-BY-NC-SA-2.0-FR", 152 | "CC-BY-NC-SA-2.0-UK", 153 | "CC-BY-NC-SA-2.5", 154 | "CC-BY-NC-SA-3.0", 155 | "CC-BY-NC-SA-3.0-DE", 156 | "CC-BY-NC-SA-3.0-IGO", 157 | "CC-BY-NC-SA-4.0", 158 | "CC-BY-ND-1.0", 159 | "CC-BY-ND-2.0", 160 | "CC-BY-ND-2.5", 161 | "CC-BY-ND-3.0", 162 | "CC-BY-ND-3.0-DE", 163 | "CC-BY-ND-4.0", 164 | "CC-BY-SA-1.0", 165 | "CC-BY-SA-2.0", 166 | "CC-BY-SA-2.0-UK", 167 | "CC-BY-SA-2.1-JP", 168 | "CC-BY-SA-2.5", 169 | "CC-BY-SA-3.0", 170 | "CC-BY-SA-3.0-AT", 171 | "CC-BY-SA-3.0-DE", 172 | "CC-BY-SA-3.0-IGO", 173 | "CC-BY-SA-4.0", 174 | "CC-PDDC", 175 | "CC-PDM-1.0", 176 | "CC-SA-1.0", 177 | "CC0-1.0", 178 | "CDDL-1.0", 179 | "CDDL-1.1", 180 | "CDL-1.0", 181 | "CDLA-Permissive-1.0", 182 | "CDLA-Permissive-2.0", 183 | "CDLA-Sharing-1.0", 184 | "CECILL-1.0", 185 | "CECILL-1.1", 186 | "CECILL-2.0", 187 | "CECILL-2.1", 188 | "CECILL-B", 189 | "CECILL-C", 190 | "CERN-OHL-1.1", 191 | "CERN-OHL-1.2", 192 | "CERN-OHL-P-2.0", 193 | "CERN-OHL-S-2.0", 194 | "CERN-OHL-W-2.0", 195 | "CFITSIO", 196 | "check-cvs", 197 | "checkmk", 198 | "ClArtistic", 199 | "Clips", 200 | "CMU-Mach", 201 | "CMU-Mach-nodoc", 202 | "CNRI-Jython", 203 | "CNRI-Python", 204 | "CNRI-Python-GPL-Compatible", 205 | "COIL-1.0", 206 | "Community-Spec-1.0", 207 | "Condor-1.1", 208 | "copyleft-next-0.3.0", 209 | "copyleft-next-0.3.1", 210 | "Cornell-Lossless-JPEG", 211 | "CPAL-1.0", 212 | "CPL-1.0", 213 | "CPOL-1.02", 214 | "Cronyx", 215 | "Crossword", 216 | "CryptoSwift", 217 | "CrystalStacker", 218 | "CUA-OPL-1.0", 219 | "Cube", 220 | "curl", 221 | "cve-tou", 222 | "D-FSL-1.0", 223 | "DEC-3-Clause", 224 | "diffmark", 225 | "DL-DE-BY-2.0", 226 | "DL-DE-ZERO-2.0", 227 | "DOC", 228 | "DocBook-DTD", 229 | "DocBook-Schema", 230 | "DocBook-Stylesheet", 231 | "DocBook-XML", 232 | "Dotseqn", 233 | "DRL-1.0", 234 | "DRL-1.1", 235 | "DSDP", 236 | "dtoa", 237 | "dvipdfm", 238 | "ECL-1.0", 239 | "ECL-2.0", 240 | "EFL-1.0", 241 | "EFL-2.0", 242 | "eGenix", 243 | "Elastic-2.0", 244 | "Entessa", 245 | "EPICS", 246 | "EPL-1.0", 247 | "EPL-2.0", 248 | "ErlPL-1.1", 249 | "ESA-PL-permissive-2.4", 250 | "ESA-PL-strong-copyleft-2.4", 251 | "ESA-PL-weak-copyleft-2.4", 252 | "etalab-2.0", 253 | "EUDatagrid", 254 | "EUPL-1.0", 255 | "EUPL-1.1", 256 | "EUPL-1.2", 257 | "Eurosym", 258 | "Fair", 259 | "FBM", 260 | "FDK-AAC", 261 | "Ferguson-Twofish", 262 | "Frameworx-1.0", 263 | "FreeBSD-DOC", 264 | "FreeImage", 265 | "FSFAP", 266 | "FSFAP-no-warranty-disclaimer", 267 | "FSFUL", 268 | "FSFULLR", 269 | "FSFULLRSD", 270 | "FSFULLRWD", 271 | "FSL-1.1-ALv2", 272 | "FSL-1.1-MIT", 273 | "FTL", 274 | "Furuseth", 275 | "fwlw", 276 | "Game-Programming-Gems", 277 | "GCR-docs", 278 | "GD", 279 | "generic-xts", 280 | "GFDL-1.1-invariants-only", 281 | "GFDL-1.1-invariants-or-later", 282 | "GFDL-1.1-no-invariants-only", 283 | "GFDL-1.1-no-invariants-or-later", 284 | "GFDL-1.1-only", 285 | "GFDL-1.1-or-later", 286 | "GFDL-1.2-invariants-only", 287 | "GFDL-1.2-invariants-or-later", 288 | "GFDL-1.2-no-invariants-only", 289 | "GFDL-1.2-no-invariants-or-later", 290 | "GFDL-1.2-only", 291 | "GFDL-1.2-or-later", 292 | "GFDL-1.3-invariants-only", 293 | "GFDL-1.3-invariants-or-later", 294 | "GFDL-1.3-no-invariants-only", 295 | "GFDL-1.3-no-invariants-or-later", 296 | "GFDL-1.3-only", 297 | "GFDL-1.3-or-later", 298 | "Giftware", 299 | "GL2PS", 300 | "Glide", 301 | "Glulxe", 302 | "GLWTPL", 303 | "gnuplot", 304 | "GPL-1.0-only", 305 | "GPL-1.0-or-later", 306 | "GPL-2.0-only", 307 | "GPL-2.0-or-later", 308 | "GPL-3.0-only", 309 | "GPL-3.0-or-later", 310 | "Graphics-Gems", 311 | "gSOAP-1.3b", 312 | "gtkbook", 313 | "Gutmann", 314 | "HaskellReport", 315 | "HDF5", 316 | "hdparm", 317 | "HIDAPI", 318 | "Hippocratic-2.1", 319 | "HP-1986", 320 | "HP-1989", 321 | "HPND", 322 | "HPND-DEC", 323 | "HPND-doc", 324 | "HPND-doc-sell", 325 | "HPND-export-US", 326 | "HPND-export-US-acknowledgement", 327 | "HPND-export-US-modify", 328 | "HPND-export2-US", 329 | "HPND-Fenneberg-Livingston", 330 | "HPND-INRIA-IMAG", 331 | "HPND-Intel", 332 | "HPND-Kevlin-Henney", 333 | "HPND-Markus-Kuhn", 334 | "HPND-merchantability-variant", 335 | "HPND-MIT-disclaimer", 336 | "HPND-Netrek", 337 | "HPND-Pbmplus", 338 | "HPND-sell-MIT-disclaimer-xserver", 339 | "HPND-sell-regexpr", 340 | "HPND-sell-variant", 341 | "HPND-sell-variant-MIT-disclaimer", 342 | "HPND-sell-variant-MIT-disclaimer-rev", 343 | "HPND-SMC", 344 | "HPND-UC", 345 | "HPND-UC-export-US", 346 | "HTMLTIDY", 347 | "hyphen-bulgarian", 348 | "IBM-pibs", 349 | "ICU", 350 | "IEC-Code-Components-EULA", 351 | "IJG", 352 | "IJG-short", 353 | "ImageMagick", 354 | "iMatix", 355 | "Imlib2", 356 | "Info-ZIP", 357 | "Inner-Net-2.0", 358 | "InnoSetup", 359 | "Intel", 360 | "Intel-ACPI", 361 | "Interbase-1.0", 362 | "IPA", 363 | "IPL-1.0", 364 | "ISC", 365 | "ISC-Veillard", 366 | "ISO-permission", 367 | "Jam", 368 | "JasPer-2.0", 369 | "jove", 370 | "JPL-image", 371 | "JPNIC", 372 | "JSON", 373 | "Kastrup", 374 | "Kazlib", 375 | "Knuth-CTAN", 376 | "LAL-1.2", 377 | "LAL-1.3", 378 | "Latex2e", 379 | "Latex2e-translated-notice", 380 | "Leptonica", 381 | "LGPL-2.0-only", 382 | "LGPL-2.0-or-later", 383 | "LGPL-2.1-only", 384 | "LGPL-2.1-or-later", 385 | "LGPL-3.0-only", 386 | "LGPL-3.0-or-later", 387 | "LGPLLR", 388 | "Libpng", 389 | "libpng-1.6.35", 390 | "libpng-2.0", 391 | "libselinux-1.0", 392 | "libtiff", 393 | "libutil-David-Nugent", 394 | "LiLiQ-P-1.1", 395 | "LiLiQ-R-1.1", 396 | "LiLiQ-Rplus-1.1", 397 | "Linux-man-pages-1-para", 398 | "Linux-man-pages-copyleft", 399 | "Linux-man-pages-copyleft-2-para", 400 | "Linux-man-pages-copyleft-var", 401 | "Linux-OpenIB", 402 | "LOOP", 403 | "LPD-document", 404 | "LPL-1.0", 405 | "LPL-1.02", 406 | "LPPL-1.0", 407 | "LPPL-1.1", 408 | "LPPL-1.2", 409 | "LPPL-1.3a", 410 | "LPPL-1.3c", 411 | "lsof", 412 | "Lucida-Bitmap-Fonts", 413 | "LZMA-SDK-9.11-to-9.20", 414 | "LZMA-SDK-9.22", 415 | "Mackerras-3-Clause", 416 | "Mackerras-3-Clause-acknowledgment", 417 | "magaz", 418 | "mailprio", 419 | "MakeIndex", 420 | "man2html", 421 | "Martin-Birgmeier", 422 | "McPhee-slideshow", 423 | "metamail", 424 | "Minpack", 425 | "MIPS", 426 | "MirOS", 427 | "MIT", 428 | "MIT-0", 429 | "MIT-advertising", 430 | "MIT-Click", 431 | "MIT-CMU", 432 | "MIT-enna", 433 | "MIT-feh", 434 | "MIT-Festival", 435 | "MIT-Khronos-old", 436 | "MIT-Modern-Variant", 437 | "MIT-open-group", 438 | "MIT-STK", 439 | "MIT-testregex", 440 | "MIT-Wu", 441 | "MITNFA", 442 | "MMIXware", 443 | "Motosoto", 444 | "MPEG-SSG", 445 | "mpi-permissive", 446 | "mpich2", 447 | "MPL-1.0", 448 | "MPL-1.1", 449 | "MPL-2.0", 450 | "MPL-2.0-no-copyleft-exception", 451 | "mplus", 452 | "MS-LPL", 453 | "MS-PL", 454 | "MS-RL", 455 | "MTLL", 456 | "MulanPSL-1.0", 457 | "MulanPSL-2.0", 458 | "Multics", 459 | "Mup", 460 | "NAIST-2003", 461 | "NASA-1.3", 462 | "Naumen", 463 | "NBPL-1.0", 464 | "NCBI-PD", 465 | "NCGL-UK-2.0", 466 | "NCL", 467 | "NCSA", 468 | "NetCDF", 469 | "Newsletr", 470 | "NGPL", 471 | "ngrep", 472 | "NICTA-1.0", 473 | "NIST-PD", 474 | "NIST-PD-fallback", 475 | "NIST-PD-TNT", 476 | "NIST-Software", 477 | "NLOD-1.0", 478 | "NLOD-2.0", 479 | "NLPL", 480 | "Nokia", 481 | "NOSL", 482 | "Noweb", 483 | "NPL-1.0", 484 | "NPL-1.1", 485 | "NPOSL-3.0", 486 | "NRL", 487 | "NTIA-PD", 488 | "NTP", 489 | "NTP-0", 490 | "O-UDA-1.0", 491 | "OAR", 492 | "OCCT-PL", 493 | "OCLC-2.0", 494 | "ODbL-1.0", 495 | "ODC-By-1.0", 496 | "OFFIS", 497 | "OFL-1.0", 498 | "OFL-1.0-no-RFN", 499 | "OFL-1.0-RFN", 500 | "OFL-1.1", 501 | "OFL-1.1-no-RFN", 502 | "OFL-1.1-RFN", 503 | "OGC-1.0", 504 | "OGDL-Taiwan-1.0", 505 | "OGL-Canada-2.0", 506 | "OGL-UK-1.0", 507 | "OGL-UK-2.0", 508 | "OGL-UK-3.0", 509 | "OGTSL", 510 | "OLDAP-1.1", 511 | "OLDAP-1.2", 512 | "OLDAP-1.3", 513 | "OLDAP-1.4", 514 | "OLDAP-2.0", 515 | "OLDAP-2.0.1", 516 | "OLDAP-2.1", 517 | "OLDAP-2.2", 518 | "OLDAP-2.2.1", 519 | "OLDAP-2.2.2", 520 | "OLDAP-2.3", 521 | "OLDAP-2.4", 522 | "OLDAP-2.5", 523 | "OLDAP-2.6", 524 | "OLDAP-2.7", 525 | "OLDAP-2.8", 526 | "OLFL-1.3", 527 | "OML", 528 | "OpenPBS-2.3", 529 | "OpenSSL", 530 | "OpenSSL-standalone", 531 | "OpenVision", 532 | "OPL-1.0", 533 | "OPL-UK-3.0", 534 | "OPUBL-1.0", 535 | "OSET-PL-2.1", 536 | "OSL-1.0", 537 | "OSL-1.1", 538 | "OSL-2.0", 539 | "OSL-2.1", 540 | "OSL-3.0", 541 | "OSSP", 542 | "PADL", 543 | "Parity-6.0.0", 544 | "Parity-7.0.0", 545 | "PDDL-1.0", 546 | "PHP-3.0", 547 | "PHP-3.01", 548 | "Pixar", 549 | "pkgconf", 550 | "Plexus", 551 | "pnmstitch", 552 | "PolyForm-Noncommercial-1.0.0", 553 | "PolyForm-Small-Business-1.0.0", 554 | "PostgreSQL", 555 | "PPL", 556 | "PSF-2.0", 557 | "psfrag", 558 | "psutils", 559 | "Python-2.0", 560 | "Python-2.0.1", 561 | "python-ldap", 562 | "Qhull", 563 | "QPL-1.0", 564 | "QPL-1.0-INRIA-2004", 565 | "radvd", 566 | "Rdisc", 567 | "RHeCos-1.1", 568 | "RPL-1.1", 569 | "RPL-1.5", 570 | "RPSL-1.0", 571 | "RSA-MD", 572 | "RSCPL", 573 | "Ruby", 574 | "Ruby-pty", 575 | "SAX-PD", 576 | "SAX-PD-2.0", 577 | "Saxpath", 578 | "SCEA", 579 | "SchemeReport", 580 | "Sendmail", 581 | "Sendmail-8.23", 582 | "Sendmail-Open-Source-1.1", 583 | "SGI-B-1.0", 584 | "SGI-B-1.1", 585 | "SGI-B-2.0", 586 | "SGI-OpenGL", 587 | "SGMLUG-PM", 588 | "SGP4", 589 | "SHL-0.5", 590 | "SHL-0.51", 591 | "SimPL-2.0", 592 | "SISSL", 593 | "SISSL-1.2", 594 | "SL", 595 | "Sleepycat", 596 | "SMAIL-GPL", 597 | "SMLNJ", 598 | "SMPPL", 599 | "SNIA", 600 | "snprintf", 601 | "SOFA", 602 | "softSurfer", 603 | "Soundex", 604 | "Spencer-86", 605 | "Spencer-94", 606 | "Spencer-99", 607 | "SPL-1.0", 608 | "ssh-keyscan", 609 | "SSH-OpenSSH", 610 | "SSH-short", 611 | "SSLeay-standalone", 612 | "SSPL-1.0", 613 | "SugarCRM-1.1.3", 614 | "SUL-1.0", 615 | "Sun-PPP", 616 | "Sun-PPP-2000", 617 | "SunPro", 618 | "SWL", 619 | "swrule", 620 | "Symlinks", 621 | "TAPR-OHL-1.0", 622 | "TCL", 623 | "TCP-wrappers", 624 | "TekHVC", 625 | "TermReadKey", 626 | "TGPPL-1.0", 627 | "ThirdEye", 628 | "threeparttable", 629 | "TMate", 630 | "TORQUE-1.1", 631 | "TOSL", 632 | "TPDL", 633 | "TPL-1.0", 634 | "TrustedQSL", 635 | "TTWL", 636 | "TTYP0", 637 | "TU-Berlin-1.0", 638 | "TU-Berlin-2.0", 639 | "Ubuntu-font-1.0", 640 | "UCAR", 641 | "UCL-1.0", 642 | "ulem", 643 | "UMich-Merit", 644 | "Unicode-3.0", 645 | "Unicode-DFS-2015", 646 | "Unicode-DFS-2016", 647 | "Unicode-TOU", 648 | "UnixCrypt", 649 | "Unlicense", 650 | "Unlicense-libtelnet", 651 | "Unlicense-libwhirlpool", 652 | "UPL-1.0", 653 | "URT-RLE", 654 | "Vim", 655 | "VOSTROM", 656 | "VSL-1.0", 657 | "W3C", 658 | "W3C-19980720", 659 | "W3C-20150513", 660 | "w3m", 661 | "Watcom-1.0", 662 | "Widget-Workshop", 663 | "WordNet", 664 | "Wsuipa", 665 | "WTFNMFPL", 666 | "WTFPL", 667 | "wwl", 668 | "X11", 669 | "X11-distribute-modifications-variant", 670 | "X11-no-permit-persons", 671 | "X11-swapped", 672 | "Xdebug-1.03", 673 | "Xerox", 674 | "Xfig", 675 | "XFree86-1.1", 676 | "xinetd", 677 | "xkeyboard-config-Zinoviev", 678 | "xlock", 679 | "Xnet", 680 | "xpp", 681 | "XSkat", 682 | "xzoom", 683 | "YPL-1.0", 684 | "YPL-1.1", 685 | "Zed", 686 | "Zeeff", 687 | "Zend-2.0", 688 | "Zimbra-1.3", 689 | "Zimbra-1.4", 690 | "Zlib", 691 | "zlib-acknowledgement", 692 | "ZPL-1.1", 693 | "ZPL-2.0", 694 | "ZPL-2.1", 695 | } 696 | } 697 | -------------------------------------------------------------------------------- /cmd/exceptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "licenseListVersion": "31b6901", 3 | "exceptions": [ 4 | { 5 | "reference": "https://spdx.org/licenses/389-exception.html", 6 | "isDeprecatedLicenseId": false, 7 | "detailsUrl": "https://spdx.org/licenses/389-exception.json", 8 | "referenceNumber": 47, 9 | "name": "389 Directory Server Exception", 10 | "licenseExceptionId": "389-exception", 11 | "seeAlso": [ 12 | "http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text", 13 | "https://web.archive.org/web/20080828121337/http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text" 14 | ] 15 | }, 16 | { 17 | "reference": "https://spdx.org/licenses/Asterisk-exception.html", 18 | "isDeprecatedLicenseId": false, 19 | "detailsUrl": "https://spdx.org/licenses/Asterisk-exception.json", 20 | "referenceNumber": 3, 21 | "name": "Asterisk exception", 22 | "licenseExceptionId": "Asterisk-exception", 23 | "seeAlso": [ 24 | "https://github.com/asterisk/libpri/blob/7f91151e6bd10957c746c031c1f4a030e8146e9a/pri.c#L22", 25 | "https://github.com/asterisk/libss7/blob/03e81bcd0d28ff25d4c77c78351ddadc82ff5c3f/ss7.c#L24" 26 | ] 27 | }, 28 | { 29 | "reference": "https://spdx.org/licenses/Asterisk-linking-protocols-exception.html", 30 | "isDeprecatedLicenseId": false, 31 | "detailsUrl": "https://spdx.org/licenses/Asterisk-linking-protocols-exception.json", 32 | "referenceNumber": 4, 33 | "name": "Asterisk linking protocols exception", 34 | "licenseExceptionId": "Asterisk-linking-protocols-exception", 35 | "seeAlso": [ 36 | "https://github.com/asterisk/asterisk/blob/115d7c01e32ccf4566a99e9d74e2b88830985a0b/LICENSE#L27" 37 | ] 38 | }, 39 | { 40 | "reference": "https://spdx.org/licenses/Autoconf-exception-2.0.html", 41 | "isDeprecatedLicenseId": false, 42 | "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-2.0.json", 43 | "referenceNumber": 29, 44 | "name": "Autoconf exception 2.0", 45 | "licenseExceptionId": "Autoconf-exception-2.0", 46 | "seeAlso": [ 47 | "http://ac-archive.sourceforge.net/doc/copyright.html", 48 | "http://ftp.gnu.org/gnu/autoconf/autoconf-2.59.tar.gz" 49 | ] 50 | }, 51 | { 52 | "reference": "https://spdx.org/licenses/Autoconf-exception-3.0.html", 53 | "isDeprecatedLicenseId": false, 54 | "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-3.0.json", 55 | "referenceNumber": 49, 56 | "name": "Autoconf exception 3.0", 57 | "licenseExceptionId": "Autoconf-exception-3.0", 58 | "seeAlso": [ 59 | "http://www.gnu.org/licenses/autoconf-exception-3.0.html" 60 | ] 61 | }, 62 | { 63 | "reference": "https://spdx.org/licenses/Autoconf-exception-generic.html", 64 | "isDeprecatedLicenseId": false, 65 | "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-generic.json", 66 | "referenceNumber": 27, 67 | "name": "Autoconf generic exception", 68 | "licenseExceptionId": "Autoconf-exception-generic", 69 | "seeAlso": [ 70 | "https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright", 71 | "https://tracker.debian.org/media/packages/s/sipwitch/copyright-1.9.15-3", 72 | "https://opensource.apple.com/source/launchd/launchd-258.1/launchd/compile.auto.html", 73 | "https://git.savannah.gnu.org/gitweb/?p\u003dgnulib.git;a\u003dblob;f\u003dgnulib-tool;h\u003d029a8cf377ad8d8f2d9e54061bf2f20496ad2eef;hb\u003d73c74ba0197e6566da6882c87b1adee63e24d75c#l407" 74 | ] 75 | }, 76 | { 77 | "reference": "https://spdx.org/licenses/Autoconf-exception-generic-3.0.html", 78 | "isDeprecatedLicenseId": false, 79 | "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-generic-3.0.json", 80 | "referenceNumber": 35, 81 | "name": "Autoconf generic exception for GPL-3.0", 82 | "licenseExceptionId": "Autoconf-exception-generic-3.0", 83 | "seeAlso": [ 84 | "https://src.fedoraproject.org/rpms/redhat-rpm-config/blob/rawhide/f/config.guess" 85 | ] 86 | }, 87 | { 88 | "reference": "https://spdx.org/licenses/Autoconf-exception-macro.html", 89 | "isDeprecatedLicenseId": false, 90 | "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-macro.json", 91 | "referenceNumber": 26, 92 | "name": "Autoconf macro exception", 93 | "licenseExceptionId": "Autoconf-exception-macro", 94 | "seeAlso": [ 95 | "https://github.com/freedesktop/xorg-macros/blob/39f07f7db58ebbf3dcb64a2bf9098ed5cf3d1223/xorg-macros.m4.in", 96 | "https://www.gnu.org/software/autoconf-archive/ax_pthread.html", 97 | "https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright" 98 | ] 99 | }, 100 | { 101 | "reference": "https://spdx.org/licenses/Bison-exception-1.24.html", 102 | "isDeprecatedLicenseId": false, 103 | "detailsUrl": "https://spdx.org/licenses/Bison-exception-1.24.json", 104 | "referenceNumber": 14, 105 | "name": "Bison exception 1.24", 106 | "licenseExceptionId": "Bison-exception-1.24", 107 | "seeAlso": [ 108 | "https://github.com/arineng/rwhoisd/blob/master/rwhoisd/mkdb/y.tab.c#L180" 109 | ] 110 | }, 111 | { 112 | "reference": "https://spdx.org/licenses/Bison-exception-2.2.html", 113 | "isDeprecatedLicenseId": false, 114 | "detailsUrl": "https://spdx.org/licenses/Bison-exception-2.2.json", 115 | "referenceNumber": 72, 116 | "name": "Bison exception 2.2", 117 | "licenseExceptionId": "Bison-exception-2.2", 118 | "seeAlso": [ 119 | "http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141" 120 | ] 121 | }, 122 | { 123 | "reference": "https://spdx.org/licenses/Bootloader-exception.html", 124 | "isDeprecatedLicenseId": false, 125 | "detailsUrl": "https://spdx.org/licenses/Bootloader-exception.json", 126 | "referenceNumber": 28, 127 | "name": "Bootloader Distribution Exception", 128 | "licenseExceptionId": "Bootloader-exception", 129 | "seeAlso": [ 130 | "https://github.com/pyinstaller/pyinstaller/blob/develop/COPYING.txt" 131 | ] 132 | }, 133 | { 134 | "reference": "https://spdx.org/licenses/CGAL-linking-exception.html", 135 | "isDeprecatedLicenseId": false, 136 | "detailsUrl": "https://spdx.org/licenses/CGAL-linking-exception.json", 137 | "referenceNumber": 60, 138 | "name": "CGAL Linking Exception", 139 | "licenseExceptionId": "CGAL-linking-exception", 140 | "seeAlso": [ 141 | "https://github.com/openscad/openscad/blob/openscad-2021.01/COPYING#L3", 142 | "https://github.com/floriankirsch/OpenCSG/blob/opencsg-1-4-2-release/license.txt#L3" 143 | ] 144 | }, 145 | { 146 | "reference": "https://spdx.org/licenses/Classpath-exception-2.0.html", 147 | "isDeprecatedLicenseId": false, 148 | "detailsUrl": "https://spdx.org/licenses/Classpath-exception-2.0.json", 149 | "referenceNumber": 2, 150 | "name": "Classpath exception 2.0", 151 | "licenseExceptionId": "Classpath-exception-2.0", 152 | "seeAlso": [ 153 | "http://www.gnu.org/software/classpath/license.html", 154 | "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception" 155 | ] 156 | }, 157 | { 158 | "reference": "https://spdx.org/licenses/Classpath-exception-2.0-short.html", 159 | "isDeprecatedLicenseId": false, 160 | "detailsUrl": "https://spdx.org/licenses/Classpath-exception-2.0-short.json", 161 | "referenceNumber": 50, 162 | "name": "Classpath exception 2.0 - short", 163 | "licenseExceptionId": "Classpath-exception-2.0-short", 164 | "seeAlso": [ 165 | "https://sourceforge.net/projects/lazarus/files/Lazarus%20Zip%20_%20GZip/Lazarus%204.2/lazarus-4.2-0.tar.gz/download" 166 | ] 167 | }, 168 | { 169 | "reference": "https://spdx.org/licenses/CLISP-exception-2.0.html", 170 | "isDeprecatedLicenseId": false, 171 | "detailsUrl": "https://spdx.org/licenses/CLISP-exception-2.0.json", 172 | "referenceNumber": 20, 173 | "name": "CLISP exception 2.0", 174 | "licenseExceptionId": "CLISP-exception-2.0", 175 | "seeAlso": [ 176 | "http://sourceforge.net/p/clisp/clisp/ci/default/tree/COPYRIGHT" 177 | ] 178 | }, 179 | { 180 | "reference": "https://spdx.org/licenses/cryptsetup-OpenSSL-exception.html", 181 | "isDeprecatedLicenseId": false, 182 | "detailsUrl": "https://spdx.org/licenses/cryptsetup-OpenSSL-exception.json", 183 | "referenceNumber": 1, 184 | "name": "cryptsetup OpenSSL exception", 185 | "licenseExceptionId": "cryptsetup-OpenSSL-exception", 186 | "seeAlso": [ 187 | "https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/COPYING", 188 | "https://gitlab.nic.cz/datovka/datovka/-/blob/develop/COPYING", 189 | "https://github.com/nbs-system/naxsi/blob/951123ad456bdf5ac94e8d8819342fe3d49bc002/naxsi_src/naxsi_raw.c", 190 | "http://web.mit.edu/jgross/arch/amd64_deb60/bin/mosh", 191 | "https://sourceforge.net/p/linux-ima/ima-evm-utils/ci/master/tree/src/evmctl.c#l30", 192 | "https://github.com/ocaml-omake/omake/blob/master/LICENSE.OMake#L20" 193 | ] 194 | }, 195 | { 196 | "reference": "https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.html", 197 | "isDeprecatedLicenseId": false, 198 | "detailsUrl": "https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.json", 199 | "referenceNumber": 5, 200 | "name": "Digia Qt LGPL Exception version 1.1", 201 | "licenseExceptionId": "Digia-Qt-LGPL-exception-1.1", 202 | "seeAlso": [ 203 | "https://src.fedoraproject.org/rpms/qtlockedfile/blob/rawhide/f/LGPL_EXCEPTION" 204 | ] 205 | }, 206 | { 207 | "reference": "https://spdx.org/licenses/DigiRule-FOSS-exception.html", 208 | "isDeprecatedLicenseId": false, 209 | "detailsUrl": "https://spdx.org/licenses/DigiRule-FOSS-exception.json", 210 | "referenceNumber": 78, 211 | "name": "DigiRule FOSS License Exception", 212 | "licenseExceptionId": "DigiRule-FOSS-exception", 213 | "seeAlso": [ 214 | "http://www.digirulesolutions.com/drupal/foss" 215 | ] 216 | }, 217 | { 218 | "reference": "https://spdx.org/licenses/eCos-exception-2.0.html", 219 | "isDeprecatedLicenseId": false, 220 | "detailsUrl": "https://spdx.org/licenses/eCos-exception-2.0.json", 221 | "referenceNumber": 34, 222 | "name": "eCos exception 2.0", 223 | "licenseExceptionId": "eCos-exception-2.0", 224 | "seeAlso": [ 225 | "http://ecos.sourceware.org/license-overview.html" 226 | ] 227 | }, 228 | { 229 | "reference": "https://spdx.org/licenses/erlang-otp-linking-exception.html", 230 | "isDeprecatedLicenseId": false, 231 | "detailsUrl": "https://spdx.org/licenses/erlang-otp-linking-exception.json", 232 | "referenceNumber": 25, 233 | "name": "Erlang/OTP Linking Exception", 234 | "licenseExceptionId": "erlang-otp-linking-exception", 235 | "seeAlso": [ 236 | "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs", 237 | "https://erlang.org/pipermail/erlang-questions/2012-May/066355.html", 238 | "https://gitea.osmocom.org/erlang/osmo_ss7/src/commit/2286c1b8738d715950026650bf53f19a69d6ed0e/src/ss7_links.erl#L20" 239 | ] 240 | }, 241 | { 242 | "reference": "https://spdx.org/licenses/Fawkes-Runtime-exception.html", 243 | "isDeprecatedLicenseId": false, 244 | "detailsUrl": "https://spdx.org/licenses/Fawkes-Runtime-exception.json", 245 | "referenceNumber": 24, 246 | "name": "Fawkes Runtime Exception", 247 | "licenseExceptionId": "Fawkes-Runtime-exception", 248 | "seeAlso": [ 249 | "http://www.fawkesrobotics.org/about/license/" 250 | ] 251 | }, 252 | { 253 | "reference": "https://spdx.org/licenses/FLTK-exception.html", 254 | "isDeprecatedLicenseId": false, 255 | "detailsUrl": "https://spdx.org/licenses/FLTK-exception.json", 256 | "referenceNumber": 31, 257 | "name": "FLTK exception", 258 | "licenseExceptionId": "FLTK-exception", 259 | "seeAlso": [ 260 | "http://www.fltk.org/COPYING.php" 261 | ] 262 | }, 263 | { 264 | "reference": "https://spdx.org/licenses/fmt-exception.html", 265 | "isDeprecatedLicenseId": false, 266 | "detailsUrl": "https://spdx.org/licenses/fmt-exception.json", 267 | "referenceNumber": 70, 268 | "name": "fmt exception", 269 | "licenseExceptionId": "fmt-exception", 270 | "seeAlso": [ 271 | "https://github.com/fmtlib/fmt/blob/master/LICENSE", 272 | "https://github.com/fmtlib/fmt/blob/2eb363297b24cd71a68ccfb20ff755430f17e60f/LICENSE#L22C1-L27C62" 273 | ] 274 | }, 275 | { 276 | "reference": "https://spdx.org/licenses/Font-exception-2.0.html", 277 | "isDeprecatedLicenseId": false, 278 | "detailsUrl": "https://spdx.org/licenses/Font-exception-2.0.json", 279 | "referenceNumber": 37, 280 | "name": "Font exception 2.0", 281 | "licenseExceptionId": "Font-exception-2.0", 282 | "seeAlso": [ 283 | "http://www.gnu.org/licenses/gpl-faq.html#FontException" 284 | ] 285 | }, 286 | { 287 | "reference": "https://spdx.org/licenses/freertos-exception-2.0.html", 288 | "isDeprecatedLicenseId": false, 289 | "detailsUrl": "https://spdx.org/licenses/freertos-exception-2.0.json", 290 | "referenceNumber": 36, 291 | "name": "FreeRTOS Exception 2.0", 292 | "licenseExceptionId": "freertos-exception-2.0", 293 | "seeAlso": [ 294 | "https://web.archive.org/web/20060809182744/http://www.freertos.org/a00114.html" 295 | ] 296 | }, 297 | { 298 | "reference": "https://spdx.org/licenses/GCC-exception-2.0.html", 299 | "isDeprecatedLicenseId": false, 300 | "detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0.json", 301 | "referenceNumber": 76, 302 | "name": "GCC Runtime Library exception 2.0", 303 | "licenseExceptionId": "GCC-exception-2.0", 304 | "seeAlso": [ 305 | "https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10", 306 | "https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dcsu/abi-note.c;h\u003dc2ec208e94fbe91f63d3c375bd254b884695d190;hb\u003dHEAD" 307 | ] 308 | }, 309 | { 310 | "reference": "https://spdx.org/licenses/GCC-exception-2.0-note.html", 311 | "isDeprecatedLicenseId": false, 312 | "detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0-note.json", 313 | "referenceNumber": 51, 314 | "name": "GCC Runtime Library exception 2.0 - note variant", 315 | "licenseExceptionId": "GCC-exception-2.0-note", 316 | "seeAlso": [ 317 | "https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dsysdeps/x86_64/start.S" 318 | ] 319 | }, 320 | { 321 | "reference": "https://spdx.org/licenses/GCC-exception-3.1.html", 322 | "isDeprecatedLicenseId": false, 323 | "detailsUrl": "https://spdx.org/licenses/GCC-exception-3.1.json", 324 | "referenceNumber": 46, 325 | "name": "GCC Runtime Library exception 3.1", 326 | "licenseExceptionId": "GCC-exception-3.1", 327 | "seeAlso": [ 328 | "http://www.gnu.org/licenses/gcc-exception-3.1.html" 329 | ] 330 | }, 331 | { 332 | "reference": "https://spdx.org/licenses/Gmsh-exception.html", 333 | "isDeprecatedLicenseId": false, 334 | "detailsUrl": "https://spdx.org/licenses/Gmsh-exception.json", 335 | "referenceNumber": 21, 336 | "name": "Gmsh exception", 337 | "licenseExceptionId": "Gmsh-exception", 338 | "seeAlso": [ 339 | "https://gitlab.onelab.info/gmsh/gmsh/-/raw/master/LICENSE.txt" 340 | ] 341 | }, 342 | { 343 | "reference": "https://spdx.org/licenses/GNAT-exception.html", 344 | "isDeprecatedLicenseId": false, 345 | "detailsUrl": "https://spdx.org/licenses/GNAT-exception.json", 346 | "referenceNumber": 19, 347 | "name": "GNAT exception", 348 | "licenseExceptionId": "GNAT-exception", 349 | "seeAlso": [ 350 | "https://github.com/AdaCore/florist/blob/master/libsrc/posix-configurable_file_limits.adb" 351 | ] 352 | }, 353 | { 354 | "reference": "https://spdx.org/licenses/GNOME-examples-exception.html", 355 | "isDeprecatedLicenseId": false, 356 | "detailsUrl": "https://spdx.org/licenses/GNOME-examples-exception.json", 357 | "referenceNumber": 33, 358 | "name": "GNOME examples exception", 359 | "licenseExceptionId": "GNOME-examples-exception", 360 | "seeAlso": [ 361 | "https://gitlab.gnome.org/Archive/gnome-devel-docs/-/blob/master/platform-demos/C/legal.xml?ref_type\u003dheads", 362 | "http://meldmerge.org/help/" 363 | ] 364 | }, 365 | { 366 | "reference": "https://spdx.org/licenses/GNU-compiler-exception.html", 367 | "isDeprecatedLicenseId": false, 368 | "detailsUrl": "https://spdx.org/licenses/GNU-compiler-exception.json", 369 | "referenceNumber": 18, 370 | "name": "GNU Compiler Exception", 371 | "licenseExceptionId": "GNU-compiler-exception", 372 | "seeAlso": [ 373 | "https://sourceware.org/git?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/unlink-if-ordinary.c;h\u003de49f2f2f67bfdb10d6b2bd579b0e01cad0fd708e;hb\u003dHEAD#l19", 374 | "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/powerpc/lib/crtsavres.S?h\u003dv6.16-rc6#n34" 375 | ] 376 | }, 377 | { 378 | "reference": "https://spdx.org/licenses/gnu-javamail-exception.html", 379 | "isDeprecatedLicenseId": false, 380 | "detailsUrl": "https://spdx.org/licenses/gnu-javamail-exception.json", 381 | "referenceNumber": 69, 382 | "name": "GNU JavaMail exception", 383 | "licenseExceptionId": "gnu-javamail-exception", 384 | "seeAlso": [ 385 | "http://www.gnu.org/software/classpathx/javamail/javamail.html" 386 | ] 387 | }, 388 | { 389 | "reference": "https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.html", 390 | "isDeprecatedLicenseId": false, 391 | "detailsUrl": "https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.json", 392 | "referenceNumber": 12, 393 | "name": "GPL-3.0 389 DS Base Exception", 394 | "licenseExceptionId": "GPL-3.0-389-ds-base-exception", 395 | "seeAlso": [] 396 | }, 397 | { 398 | "reference": "https://spdx.org/licenses/GPL-3.0-interface-exception.html", 399 | "isDeprecatedLicenseId": false, 400 | "detailsUrl": "https://spdx.org/licenses/GPL-3.0-interface-exception.json", 401 | "referenceNumber": 11, 402 | "name": "GPL-3.0 Interface Exception", 403 | "licenseExceptionId": "GPL-3.0-interface-exception", 404 | "seeAlso": [ 405 | "https://www.gnu.org/licenses/gpl-faq.en.html#LinkingOverControlledInterface" 406 | ] 407 | }, 408 | { 409 | "reference": "https://spdx.org/licenses/GPL-3.0-linking-exception.html", 410 | "isDeprecatedLicenseId": false, 411 | "detailsUrl": "https://spdx.org/licenses/GPL-3.0-linking-exception.json", 412 | "referenceNumber": 57, 413 | "name": "GPL-3.0 Linking Exception", 414 | "licenseExceptionId": "GPL-3.0-linking-exception", 415 | "seeAlso": [ 416 | "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs" 417 | ] 418 | }, 419 | { 420 | "reference": "https://spdx.org/licenses/GPL-3.0-linking-source-exception.html", 421 | "isDeprecatedLicenseId": false, 422 | "detailsUrl": "https://spdx.org/licenses/GPL-3.0-linking-source-exception.json", 423 | "referenceNumber": 67, 424 | "name": "GPL-3.0 Linking Exception (with Corresponding Source)", 425 | "licenseExceptionId": "GPL-3.0-linking-source-exception", 426 | "seeAlso": [ 427 | "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs", 428 | "https://github.com/mirror/wget/blob/master/src/http.c#L20" 429 | ] 430 | }, 431 | { 432 | "reference": "https://spdx.org/licenses/GPL-CC-1.0.html", 433 | "isDeprecatedLicenseId": false, 434 | "detailsUrl": "https://spdx.org/licenses/GPL-CC-1.0.json", 435 | "referenceNumber": 38, 436 | "name": "GPL Cooperation Commitment 1.0", 437 | "licenseExceptionId": "GPL-CC-1.0", 438 | "seeAlso": [ 439 | "https://github.com/gplcc/gplcc/blob/master/Project/COMMITMENT", 440 | "https://gplcc.github.io/gplcc/Project/README-PROJECT.html" 441 | ] 442 | }, 443 | { 444 | "reference": "https://spdx.org/licenses/GStreamer-exception-2005.html", 445 | "isDeprecatedLicenseId": false, 446 | "detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2005.json", 447 | "referenceNumber": 15, 448 | "name": "GStreamer Exception (2005)", 449 | "licenseExceptionId": "GStreamer-exception-2005", 450 | "seeAlso": [ 451 | "https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer" 452 | ] 453 | }, 454 | { 455 | "reference": "https://spdx.org/licenses/GStreamer-exception-2008.html", 456 | "isDeprecatedLicenseId": false, 457 | "detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2008.json", 458 | "referenceNumber": 39, 459 | "name": "GStreamer Exception (2008)", 460 | "licenseExceptionId": "GStreamer-exception-2008", 461 | "seeAlso": [ 462 | "https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer" 463 | ] 464 | }, 465 | { 466 | "reference": "https://spdx.org/licenses/harbour-exception.html", 467 | "isDeprecatedLicenseId": false, 468 | "detailsUrl": "https://spdx.org/licenses/harbour-exception.json", 469 | "referenceNumber": 58, 470 | "name": "harbour exception", 471 | "licenseExceptionId": "harbour-exception", 472 | "seeAlso": [ 473 | "https://github.com/harbour/core/blob/master/LICENSE.txt#L44-L66" 474 | ] 475 | }, 476 | { 477 | "reference": "https://spdx.org/licenses/i2p-gpl-java-exception.html", 478 | "isDeprecatedLicenseId": false, 479 | "detailsUrl": "https://spdx.org/licenses/i2p-gpl-java-exception.json", 480 | "referenceNumber": 56, 481 | "name": "i2p GPL+Java Exception", 482 | "licenseExceptionId": "i2p-gpl-java-exception", 483 | "seeAlso": [ 484 | "http://geti2p.net/en/get-involved/develop/licenses#java_exception" 485 | ] 486 | }, 487 | { 488 | "reference": "https://spdx.org/licenses/Independent-modules-exception.html", 489 | "isDeprecatedLicenseId": false, 490 | "detailsUrl": "https://spdx.org/licenses/Independent-modules-exception.json", 491 | "referenceNumber": 9, 492 | "name": "Independent Module Linking exception", 493 | "licenseExceptionId": "Independent-modules-exception", 494 | "seeAlso": [ 495 | "https://gitlab.com/freepascal.org/fpc/source/-/blob/release_3_2_2/rtl/COPYING.FPC" 496 | ] 497 | }, 498 | { 499 | "reference": "https://spdx.org/licenses/KiCad-libraries-exception.html", 500 | "isDeprecatedLicenseId": false, 501 | "detailsUrl": "https://spdx.org/licenses/KiCad-libraries-exception.json", 502 | "referenceNumber": 44, 503 | "name": "KiCad Libraries Exception", 504 | "licenseExceptionId": "KiCad-libraries-exception", 505 | "seeAlso": [ 506 | "https://www.kicad.org/libraries/license/" 507 | ] 508 | }, 509 | { 510 | "reference": "https://spdx.org/licenses/kvirc-openssl-exception.html", 511 | "isDeprecatedLicenseId": false, 512 | "detailsUrl": "https://spdx.org/licenses/kvirc-openssl-exception.json", 513 | "referenceNumber": 32, 514 | "name": "kvirc OpenSSL Exception", 515 | "licenseExceptionId": "kvirc-openssl-exception", 516 | "seeAlso": [ 517 | "https://github.com/kvirc/KVIrc/blob/ba18690abb4f5ce77bb10164ee0835cc150f4a2a/doc/ABOUT-LICENSE#L34" 518 | ] 519 | }, 520 | { 521 | "reference": "https://spdx.org/licenses/LGPL-3.0-linking-exception.html", 522 | "isDeprecatedLicenseId": false, 523 | "detailsUrl": "https://spdx.org/licenses/LGPL-3.0-linking-exception.json", 524 | "referenceNumber": 54, 525 | "name": "LGPL-3.0 Linking Exception", 526 | "licenseExceptionId": "LGPL-3.0-linking-exception", 527 | "seeAlso": [ 528 | "https://raw.githubusercontent.com/go-xmlpath/xmlpath/v2/LICENSE", 529 | "https://github.com/goamz/goamz/blob/master/LICENSE", 530 | "https://github.com/juju/errors/blob/master/LICENSE" 531 | ] 532 | }, 533 | { 534 | "reference": "https://spdx.org/licenses/libpri-OpenH323-exception.html", 535 | "isDeprecatedLicenseId": false, 536 | "detailsUrl": "https://spdx.org/licenses/libpri-OpenH323-exception.json", 537 | "referenceNumber": 79, 538 | "name": "libpri OpenH323 exception", 539 | "licenseExceptionId": "libpri-OpenH323-exception", 540 | "seeAlso": [ 541 | "https://github.com/asterisk/libpri/blob/1.6.0/README#L19-L22" 542 | ] 543 | }, 544 | { 545 | "reference": "https://spdx.org/licenses/Libtool-exception.html", 546 | "isDeprecatedLicenseId": false, 547 | "detailsUrl": "https://spdx.org/licenses/Libtool-exception.json", 548 | "referenceNumber": 13, 549 | "name": "Libtool Exception", 550 | "licenseExceptionId": "Libtool-exception", 551 | "seeAlso": [ 552 | "http://git.savannah.gnu.org/cgit/libtool.git/tree/m4/libtool.m4", 553 | "https://git.savannah.gnu.org/cgit/libtool.git/tree/libltdl/lt__alloc.c#n15" 554 | ] 555 | }, 556 | { 557 | "reference": "https://spdx.org/licenses/Linux-syscall-note.html", 558 | "isDeprecatedLicenseId": false, 559 | "detailsUrl": "https://spdx.org/licenses/Linux-syscall-note.json", 560 | "referenceNumber": 42, 561 | "name": "Linux Syscall Note", 562 | "licenseExceptionId": "Linux-syscall-note", 563 | "seeAlso": [ 564 | "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/COPYING" 565 | ] 566 | }, 567 | { 568 | "reference": "https://spdx.org/licenses/LLGPL.html", 569 | "isDeprecatedLicenseId": false, 570 | "detailsUrl": "https://spdx.org/licenses/LLGPL.json", 571 | "referenceNumber": 45, 572 | "name": "LLGPL Preamble", 573 | "licenseExceptionId": "LLGPL", 574 | "seeAlso": [ 575 | "http://opensource.franz.com/preamble.html" 576 | ] 577 | }, 578 | { 579 | "reference": "https://spdx.org/licenses/LLVM-exception.html", 580 | "isDeprecatedLicenseId": false, 581 | "detailsUrl": "https://spdx.org/licenses/LLVM-exception.json", 582 | "referenceNumber": 80, 583 | "name": "LLVM Exception", 584 | "licenseExceptionId": "LLVM-exception", 585 | "seeAlso": [ 586 | "http://llvm.org/foundation/relicensing/LICENSE.txt", 587 | "https://web.archive.org/web/20240423023852/https://foundation.llvm.org/relicensing/LICENSE.txt" 588 | ] 589 | }, 590 | { 591 | "reference": "https://spdx.org/licenses/LZMA-exception.html", 592 | "isDeprecatedLicenseId": false, 593 | "detailsUrl": "https://spdx.org/licenses/LZMA-exception.json", 594 | "referenceNumber": 81, 595 | "name": "LZMA exception", 596 | "licenseExceptionId": "LZMA-exception", 597 | "seeAlso": [ 598 | "http://nsis.sourceforge.net/Docs/AppendixI.html#I.6" 599 | ] 600 | }, 601 | { 602 | "reference": "https://spdx.org/licenses/mif-exception.html", 603 | "isDeprecatedLicenseId": false, 604 | "detailsUrl": "https://spdx.org/licenses/mif-exception.json", 605 | "referenceNumber": 41, 606 | "name": "Macros and Inline Functions Exception", 607 | "licenseExceptionId": "mif-exception", 608 | "seeAlso": [ 609 | "http://www.scs.stanford.edu/histar/src/lib/cppsup/exception", 610 | "http://dev.bertos.org/doxygen/", 611 | "https://www.threadingbuildingblocks.org/licensing" 612 | ] 613 | }, 614 | { 615 | "reference": "https://spdx.org/licenses/mxml-exception.html", 616 | "isDeprecatedLicenseId": false, 617 | "detailsUrl": "https://spdx.org/licenses/mxml-exception.json", 618 | "referenceNumber": 59, 619 | "name": "mxml Exception", 620 | "licenseExceptionId": "mxml-exception", 621 | "seeAlso": [ 622 | "https://github.com/michaelrsweet/mxml/blob/master/NOTICE", 623 | "https://github.com/michaelrsweet/mxml/blob/master/LICENSE" 624 | ] 625 | }, 626 | { 627 | "reference": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.html", 628 | "isDeprecatedLicenseId": true, 629 | "detailsUrl": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.json", 630 | "referenceNumber": 7, 631 | "name": "Nokia Qt LGPL exception 1.1", 632 | "licenseExceptionId": "Nokia-Qt-exception-1.1", 633 | "seeAlso": [ 634 | "https://www.keepassx.org/dev/projects/keepassx/repository/revisions/b8dfb9cc4d5133e0f09cd7533d15a4f1c19a40f2/entry/LICENSE.NOKIA-LGPL-EXCEPTION" 635 | ] 636 | }, 637 | { 638 | "reference": "https://spdx.org/licenses/OCaml-LGPL-linking-exception.html", 639 | "isDeprecatedLicenseId": false, 640 | "detailsUrl": "https://spdx.org/licenses/OCaml-LGPL-linking-exception.json", 641 | "referenceNumber": 52, 642 | "name": "OCaml LGPL Linking Exception", 643 | "licenseExceptionId": "OCaml-LGPL-linking-exception", 644 | "seeAlso": [ 645 | "https://caml.inria.fr/ocaml/license.en.html" 646 | ] 647 | }, 648 | { 649 | "reference": "https://spdx.org/licenses/OCCT-exception-1.0.html", 650 | "isDeprecatedLicenseId": false, 651 | "detailsUrl": "https://spdx.org/licenses/OCCT-exception-1.0.json", 652 | "referenceNumber": 65, 653 | "name": "Open CASCADE Exception 1.0", 654 | "licenseExceptionId": "OCCT-exception-1.0", 655 | "seeAlso": [ 656 | "http://www.opencascade.com/content/licensing" 657 | ] 658 | }, 659 | { 660 | "reference": "https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.html", 661 | "isDeprecatedLicenseId": false, 662 | "detailsUrl": "https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.json", 663 | "referenceNumber": 22, 664 | "name": "OpenJDK Assembly exception 1.0", 665 | "licenseExceptionId": "OpenJDK-assembly-exception-1.0", 666 | "seeAlso": [ 667 | "http://openjdk.java.net/legal/assembly-exception.html" 668 | ] 669 | }, 670 | { 671 | "reference": "https://spdx.org/licenses/openvpn-openssl-exception.html", 672 | "isDeprecatedLicenseId": false, 673 | "detailsUrl": "https://spdx.org/licenses/openvpn-openssl-exception.json", 674 | "referenceNumber": 17, 675 | "name": "OpenVPN OpenSSL Exception", 676 | "licenseExceptionId": "openvpn-openssl-exception", 677 | "seeAlso": [ 678 | "http://openvpn.net/index.php/license.html", 679 | "https://github.com/psycopg/psycopg2/blob/2_9_3/LICENSE#L14" 680 | ] 681 | }, 682 | { 683 | "reference": "https://spdx.org/licenses/PCRE2-exception.html", 684 | "isDeprecatedLicenseId": false, 685 | "detailsUrl": "https://spdx.org/licenses/PCRE2-exception.json", 686 | "referenceNumber": 53, 687 | "name": "PCRE2 exception", 688 | "licenseExceptionId": "PCRE2-exception", 689 | "seeAlso": [ 690 | "https://www.pcre.org/licence.txt" 691 | ] 692 | }, 693 | { 694 | "reference": "https://spdx.org/licenses/polyparse-exception.html", 695 | "isDeprecatedLicenseId": false, 696 | "detailsUrl": "https://spdx.org/licenses/polyparse-exception.json", 697 | "referenceNumber": 6, 698 | "name": "Polyparse Exception", 699 | "licenseExceptionId": "polyparse-exception", 700 | "seeAlso": [ 701 | "https://hackage.haskell.org/package/polyparse-1.13/src/COPYRIGHT" 702 | ] 703 | }, 704 | { 705 | "reference": "https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.html", 706 | "isDeprecatedLicenseId": false, 707 | "detailsUrl": "https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.json", 708 | "referenceNumber": 64, 709 | "name": "PS/PDF font exception (2017-08-17)", 710 | "licenseExceptionId": "PS-or-PDF-font-exception-20170817", 711 | "seeAlso": [ 712 | "https://github.com/ArtifexSoftware/urw-base35-fonts/blob/65962e27febc3883a17e651cdb23e783668c996f/LICENSE" 713 | ] 714 | }, 715 | { 716 | "reference": "https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.html", 717 | "isDeprecatedLicenseId": false, 718 | "detailsUrl": "https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.json", 719 | "referenceNumber": 10, 720 | "name": "INRIA QPL 1.0 2004 variant exception", 721 | "licenseExceptionId": "QPL-1.0-INRIA-2004-exception", 722 | "seeAlso": [ 723 | "https://git.frama-c.com/pub/frama-c/-/blob/master/licenses/Q_MODIFIED_LICENSE", 724 | "https://github.com/maranget/hevea/blob/master/LICENSE" 725 | ] 726 | }, 727 | { 728 | "reference": "https://spdx.org/licenses/Qt-GPL-exception-1.0.html", 729 | "isDeprecatedLicenseId": false, 730 | "detailsUrl": "https://spdx.org/licenses/Qt-GPL-exception-1.0.json", 731 | "referenceNumber": 62, 732 | "name": "Qt GPL exception 1.0", 733 | "licenseExceptionId": "Qt-GPL-exception-1.0", 734 | "seeAlso": [ 735 | "http://code.qt.io/cgit/qt/qtbase.git/tree/LICENSE.GPL3-EXCEPT" 736 | ] 737 | }, 738 | { 739 | "reference": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.html", 740 | "isDeprecatedLicenseId": false, 741 | "detailsUrl": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.json", 742 | "referenceNumber": 77, 743 | "name": "Qt LGPL exception 1.1", 744 | "licenseExceptionId": "Qt-LGPL-exception-1.1", 745 | "seeAlso": [ 746 | "http://code.qt.io/cgit/qt/qtbase.git/tree/LGPL_EXCEPTION.txt" 747 | ] 748 | }, 749 | { 750 | "reference": "https://spdx.org/licenses/Qwt-exception-1.0.html", 751 | "isDeprecatedLicenseId": false, 752 | "detailsUrl": "https://spdx.org/licenses/Qwt-exception-1.0.json", 753 | "referenceNumber": 73, 754 | "name": "Qwt exception 1.0", 755 | "licenseExceptionId": "Qwt-exception-1.0", 756 | "seeAlso": [ 757 | "http://qwt.sourceforge.net/qwtlicense.html" 758 | ] 759 | }, 760 | { 761 | "reference": "https://spdx.org/licenses/romic-exception.html", 762 | "isDeprecatedLicenseId": false, 763 | "detailsUrl": "https://spdx.org/licenses/romic-exception.json", 764 | "referenceNumber": 74, 765 | "name": "Romic Exception", 766 | "licenseExceptionId": "romic-exception", 767 | "seeAlso": [ 768 | "https://web.archive.org/web/20210124015834/http://mo.morsi.org/blog/2009/08/13/lesser_affero_gplv3/", 769 | "https://sourceforge.net/p/romic/code/ci/3ab2856180cf0d8b007609af53154cf092efc58f/tree/COPYING", 770 | "https://github.com/moll/node-mitm/blob/bbf24b8bd7596dc6e091e625363161ce91984fc7/LICENSE#L8-L11", 771 | "https://github.com/zenbones/SmallMind/blob/3c62b5995fe7f27c453f140ff9b60560a0893f2a/COPYRIGHT#L25-L30", 772 | "https://github.com/CubeArtisan/cubeartisan/blob/2c6ab53455237b88a3ea07be02a838a135c4ab79/LICENSE.LESSER#L10-L15", 773 | "https://github.com/savearray2/py.js/blob/b781273c08c8afa89f4954de4ecf42ec01429bae/README.md#license" 774 | ] 775 | }, 776 | { 777 | "reference": "https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.html", 778 | "isDeprecatedLicenseId": false, 779 | "detailsUrl": "https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.json", 780 | "referenceNumber": 68, 781 | "name": "RRDtool FLOSS exception 2.0", 782 | "licenseExceptionId": "RRDtool-FLOSS-exception-2.0", 783 | "seeAlso": [ 784 | "https://github.com/oetiker/rrdtool-1.x/blob/master/COPYRIGHT#L25-L90", 785 | "https://oss.oetiker.ch/rrdtool/license.en.html" 786 | ] 787 | }, 788 | { 789 | "reference": "https://spdx.org/licenses/SANE-exception.html", 790 | "isDeprecatedLicenseId": false, 791 | "detailsUrl": "https://spdx.org/licenses/SANE-exception.json", 792 | "referenceNumber": 61, 793 | "name": "SANE Exception", 794 | "licenseExceptionId": "SANE-exception", 795 | "seeAlso": [ 796 | "https://github.com/alexpevzner/sane-airscan/blob/master/LICENSE", 797 | "https://gitlab.com/sane-project/backends/-/blob/master/sanei/sanei_pp.c?ref_type\u003dheads", 798 | "https://gitlab.com/sane-project/frontends/-/blob/master/sanei/sanei_codec_ascii.c?ref_type\u003dheads" 799 | ] 800 | }, 801 | { 802 | "reference": "https://spdx.org/licenses/SHL-2.0.html", 803 | "isDeprecatedLicenseId": false, 804 | "detailsUrl": "https://spdx.org/licenses/SHL-2.0.json", 805 | "referenceNumber": 23, 806 | "name": "Solderpad Hardware License v2.0", 807 | "licenseExceptionId": "SHL-2.0", 808 | "seeAlso": [ 809 | "https://solderpad.org/licenses/SHL-2.0/" 810 | ] 811 | }, 812 | { 813 | "reference": "https://spdx.org/licenses/SHL-2.1.html", 814 | "isDeprecatedLicenseId": false, 815 | "detailsUrl": "https://spdx.org/licenses/SHL-2.1.json", 816 | "referenceNumber": 43, 817 | "name": "Solderpad Hardware License v2.1", 818 | "licenseExceptionId": "SHL-2.1", 819 | "seeAlso": [ 820 | "https://solderpad.org/licenses/SHL-2.1/" 821 | ] 822 | }, 823 | { 824 | "reference": "https://spdx.org/licenses/Simple-Library-Usage-exception.html", 825 | "isDeprecatedLicenseId": false, 826 | "detailsUrl": "https://spdx.org/licenses/Simple-Library-Usage-exception.json", 827 | "referenceNumber": 8, 828 | "name": "Simple Library Usage Exception", 829 | "licenseExceptionId": "Simple-Library-Usage-exception", 830 | "seeAlso": [ 831 | "https://sourceforge.net/p/teem/code/HEAD/tree/teem/trunk/LICENSE.txt" 832 | ] 833 | }, 834 | { 835 | "reference": "https://spdx.org/licenses/stunnel-exception.html", 836 | "isDeprecatedLicenseId": false, 837 | "detailsUrl": "https://spdx.org/licenses/stunnel-exception.json", 838 | "referenceNumber": 66, 839 | "name": "stunnel Exception", 840 | "licenseExceptionId": "stunnel-exception", 841 | "seeAlso": [ 842 | "https://github.com/mtrojnar/stunnel/blob/master/COPYING.md" 843 | ] 844 | }, 845 | { 846 | "reference": "https://spdx.org/licenses/SWI-exception.html", 847 | "isDeprecatedLicenseId": false, 848 | "detailsUrl": "https://spdx.org/licenses/SWI-exception.json", 849 | "referenceNumber": 75, 850 | "name": "SWI exception", 851 | "licenseExceptionId": "SWI-exception", 852 | "seeAlso": [ 853 | "https://github.com/SWI-Prolog/packages-clpqr/blob/bfa80b9270274f0800120d5b8e6fef42ac2dc6a5/clpqr/class.pl" 854 | ] 855 | }, 856 | { 857 | "reference": "https://spdx.org/licenses/Swift-exception.html", 858 | "isDeprecatedLicenseId": false, 859 | "detailsUrl": "https://spdx.org/licenses/Swift-exception.json", 860 | "referenceNumber": 55, 861 | "name": "Swift Exception", 862 | "licenseExceptionId": "Swift-exception", 863 | "seeAlso": [ 864 | "https://swift.org/LICENSE.txt", 865 | "https://github.com/apple/swift-package-manager/blob/7ab2275f447a5eb37497ed63a9340f8a6d1e488b/LICENSE.txt#L205" 866 | ] 867 | }, 868 | { 869 | "reference": "https://spdx.org/licenses/Texinfo-exception.html", 870 | "isDeprecatedLicenseId": false, 871 | "detailsUrl": "https://spdx.org/licenses/Texinfo-exception.json", 872 | "referenceNumber": 71, 873 | "name": "Texinfo exception", 874 | "licenseExceptionId": "Texinfo-exception", 875 | "seeAlso": [ 876 | "https://git.savannah.gnu.org/cgit/automake.git/tree/lib/texinfo.tex?h\u003dv1.16.5#n23" 877 | ] 878 | }, 879 | { 880 | "reference": "https://spdx.org/licenses/u-boot-exception-2.0.html", 881 | "isDeprecatedLicenseId": false, 882 | "detailsUrl": "https://spdx.org/licenses/u-boot-exception-2.0.json", 883 | "referenceNumber": 82, 884 | "name": "U-Boot exception 2.0", 885 | "licenseExceptionId": "u-boot-exception-2.0", 886 | "seeAlso": [ 887 | "http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003dLicenses/Exceptions" 888 | ] 889 | }, 890 | { 891 | "reference": "https://spdx.org/licenses/UBDL-exception.html", 892 | "isDeprecatedLicenseId": false, 893 | "detailsUrl": "https://spdx.org/licenses/UBDL-exception.json", 894 | "referenceNumber": 30, 895 | "name": "Unmodified Binary Distribution exception", 896 | "licenseExceptionId": "UBDL-exception", 897 | "seeAlso": [ 898 | "https://github.com/ipxe/ipxe/blob/master/COPYING.UBDL" 899 | ] 900 | }, 901 | { 902 | "reference": "https://spdx.org/licenses/Universal-FOSS-exception-1.0.html", 903 | "isDeprecatedLicenseId": false, 904 | "detailsUrl": "https://spdx.org/licenses/Universal-FOSS-exception-1.0.json", 905 | "referenceNumber": 48, 906 | "name": "Universal FOSS Exception, Version 1.0", 907 | "licenseExceptionId": "Universal-FOSS-exception-1.0", 908 | "seeAlso": [ 909 | "https://oss.oracle.com/licenses/universal-foss-exception/" 910 | ] 911 | }, 912 | { 913 | "reference": "https://spdx.org/licenses/vsftpd-openssl-exception.html", 914 | "isDeprecatedLicenseId": false, 915 | "detailsUrl": "https://spdx.org/licenses/vsftpd-openssl-exception.json", 916 | "referenceNumber": 63, 917 | "name": "vsftpd OpenSSL exception", 918 | "licenseExceptionId": "vsftpd-openssl-exception", 919 | "seeAlso": [ 920 | "https://git.stg.centos.org/source-git/vsftpd/blob/f727873674d9c9cd7afcae6677aa782eb54c8362/f/LICENSE", 921 | "https://launchpad.net/debian/squeeze/+source/vsftpd/+copyright", 922 | "https://github.com/richardcochran/vsftpd/blob/master/COPYING" 923 | ] 924 | }, 925 | { 926 | "reference": "https://spdx.org/licenses/WxWindows-exception-3.1.html", 927 | "isDeprecatedLicenseId": false, 928 | "detailsUrl": "https://spdx.org/licenses/WxWindows-exception-3.1.json", 929 | "referenceNumber": 16, 930 | "name": "WxWindows Library Exception 3.1", 931 | "licenseExceptionId": "WxWindows-exception-3.1", 932 | "seeAlso": [ 933 | "http://www.opensource.org/licenses/WXwindows" 934 | ] 935 | }, 936 | { 937 | "reference": "https://spdx.org/licenses/x11vnc-openssl-exception.html", 938 | "isDeprecatedLicenseId": false, 939 | "detailsUrl": "https://spdx.org/licenses/x11vnc-openssl-exception.json", 940 | "referenceNumber": 40, 941 | "name": "x11vnc OpenSSL Exception", 942 | "licenseExceptionId": "x11vnc-openssl-exception", 943 | "seeAlso": [ 944 | "https://github.com/LibVNC/x11vnc/blob/master/src/8to24.c#L22" 945 | ] 946 | } 947 | ], 948 | "releaseDate": "2025-11-28T00:00:00Z" 949 | } --------------------------------------------------------------------------------