diff --git a/cmd/doc.go b/cmd/doc.go index d20ae72..33da144 100644 --- a/cmd/doc.go +++ b/cmd/doc.go @@ -10,6 +10,7 @@ spdxexp/license.go file. Command to run all extractions (run command from the /cmd directory): + cd cmd go run . extract -l -e Usage options: diff --git a/cmd/exceptions.go b/cmd/exceptions.go index 749bf5d..bc094f2 100644 --- a/cmd/exceptions.go +++ b/cmd/exceptions.go @@ -3,7 +3,9 @@ package main import ( "encoding/json" "fmt" + "go/format" "os" + "strings" ) type ExceptionData struct { @@ -54,6 +56,27 @@ func extractExceptionLicenseIDs() error { // Code generated by go-spdx cmd/exceptions.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsException does a case-insensitive lookup for the exception id in the exceptions map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsException(id string) (bool, string) { + foundID, ok := exceptionsMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetExceptionsMap returns a map of exception license IDs keyed by uppercase ID. +func GetExceptionsMap() map[string]string { + copied := make(map[string]string, len(exceptionsMap)) + for k, v := range exceptionsMap { + copied[k] = v + } + return copied +} + // GetExceptions returns a slice of exception license IDs. func GetExceptions() []string { return []string{ @@ -64,8 +87,23 @@ func GetExceptions() []string { } getExceptionsContents = append(getExceptionsContents, ` } } + +`...) + + getExceptionsContents = append(getExceptionsContents, `var exceptionsMap = map[string]string{ +`...) + for _, id := range exceptionLicenseIDs { + getExceptionsContents = append(getExceptionsContents, ` "`+strings.ToUpper(id)+`": "`+id+`", +`...) + } + getExceptionsContents = append(getExceptionsContents, `} `...) + getExceptionsContents, err = format.Source(getExceptionsContents) + if err != nil { + return fmt.Errorf("format generated get_exceptions.go: %w", err) + } + err = os.WriteFile("../spdxexp/spdxlicenses/get_exceptions.go", getExceptionsContents, 0600) if err != nil { return err diff --git a/cmd/license.go b/cmd/license.go index c051189..22f1366 100644 --- a/cmd/license.go +++ b/cmd/license.go @@ -3,7 +3,9 @@ package main import ( "encoding/json" "fmt" + "go/format" "os" + "strings" ) type LicenseData struct { @@ -59,6 +61,27 @@ func extractLicenseIDs() error { // Code generated by go-spdx cmd/license.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsActiveLicense does a case-insensitive lookup for the license id in the active licenses map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsActiveLicense(id string) (bool, string) { + foundID, ok := licensesMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetLicensesMap returns a map of active license IDs keyed by uppercase ID. +func GetLicensesMap() map[string]string { + copied := make(map[string]string, len(licensesMap)) + for k, v := range licensesMap { + copied[k] = v + } + return copied +} + // GetLicenses returns a slice of active license IDs. func GetLicenses() []string { return []string{ @@ -69,8 +92,23 @@ func GetLicenses() []string { } getLicensesContents = append(getLicensesContents, ` } } + +`...) + + getLicensesContents = append(getLicensesContents, `var licensesMap = map[string]string{ +`...) + for _, id := range activeLicenseIDs { + getLicensesContents = append(getLicensesContents, ` "`+strings.ToUpper(id)+`": "`+id+`", +`...) + } + getLicensesContents = append(getLicensesContents, `} `...) + getLicensesContents, err = format.Source(getLicensesContents) + if err != nil { + return fmt.Errorf("format generated get_licenses.go: %w", err) + } + err = os.WriteFile("../spdxexp/spdxlicenses/get_licenses.go", getLicensesContents, 0600) if err != nil { return err @@ -83,6 +121,27 @@ func GetLicenses() []string { // Code generated by go-spdx cmd/license.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsDeprecatedLicense does a case-insensitive lookup for the license id in the deprecated licenses map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsDeprecatedLicense(id string) (bool, string) { + foundID, ok := deprecatedMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetDeprecatedMap returns a map of deprecated license IDs keyed by uppercase ID. +func GetDeprecatedMap() map[string]string { + copied := make(map[string]string, len(deprecatedMap)) + for k, v := range deprecatedMap { + copied[k] = v + } + return copied +} + // GetDeprecated returns a slice of deprecated license IDs. func GetDeprecated() []string { return []string{ @@ -93,8 +152,23 @@ func GetDeprecated() []string { } getDeprecatedContents = append(getDeprecatedContents, ` } } + +`...) + + getDeprecatedContents = append(getDeprecatedContents, `var deprecatedMap = map[string]string{ +`...) + for _, id := range deprecatedLicenseIDs { + getDeprecatedContents = append(getDeprecatedContents, ` "`+strings.ToUpper(id)+`": "`+id+`", +`...) + } + getDeprecatedContents = append(getDeprecatedContents, `} `...) + getDeprecatedContents, err = format.Source(getDeprecatedContents) + if err != nil { + return fmt.Errorf("format generated get_deprecated.go: %w", err) + } + err = os.WriteFile("../spdxexp/spdxlicenses/get_deprecated.go", getDeprecatedContents, 0600) if err != nil { return err diff --git a/spdxexp/benchmark_satisfies_test.go b/spdxexp/benchmark_satisfies_test.go index 646884e..e83cef0 100644 --- a/spdxexp/benchmark_satisfies_test.go +++ b/spdxexp/benchmark_satisfies_test.go @@ -12,14 +12,18 @@ type satisfiesBenchmarkScenario struct { var satisfiesBenchmarkScenarios = []satisfiesBenchmarkScenario{ // Scenario order is used as-is in the summary table. - {"MIT", "MIT"}, - {"mit", "mit"}, - {"Apache-2.0", "Apache-2.0"}, - {"Zed", "Zed"}, - {"MIT AND Apache-2.0", "MIT AND Apache-2.0"}, - {"MIT AND Apache-2.0 OR Zed", "MIT AND Apache-2.0 OR Zed"}, - {"GPL-2.0-or-later", "GPL-2.0-or-later"}, - {"GPL-2.0+", "GPL-2.0+"}, + {"MIT--exact", "MIT"}, + {"mit--caseinsensitive", "mit"}, + {"mit--extra-space", " MIT "}, + {"Apache-2.0--active-early", "Apache-2.0"}, + {"Zed--active-end", "Zed"}, + {"MIT AND Apache-2.0--complex", "MIT AND Apache-2.0"}, + {"MIT AND Apache-2.0 OR Zed--complex", "MIT AND Apache-2.0 OR Zed"}, + {"BSD-2-Clause-FreeBSD--deprecated", "BSD-2-Clause-FreeBSD"}, + {"GPL-2.0-or-later--range", "GPL-2.0-or-later"}, + {"Apache-1.0+--plus-range", "Apache-1.0+"}, + {"LicenseRef-scancode-adobe-postscript", "LicenseRef-scancode-adobe-postscript"}, + {"DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"}, } func BenchmarkSatisfies(b *testing.B) { diff --git a/spdxexp/benchmark_validate_licenses_test.go b/spdxexp/benchmark_validate_licenses_test.go index 0f2124e..2742e2c 100644 --- a/spdxexp/benchmark_validate_licenses_test.go +++ b/spdxexp/benchmark_validate_licenses_test.go @@ -12,14 +12,18 @@ type validateLicensesBenchmarkScenario struct { var validateLicensesBenchmarkScenarios = []validateLicensesBenchmarkScenario{ // Scenario order is used as-is in the summary table. - {"MIT", []string{"MIT"}}, - {"mit", []string{"mit"}}, - {"Apache-2.0", []string{"Apache-2.0"}}, - {"Zed", []string{"Zed"}}, - {"MIT AND Apache-2.0", []string{"MIT", "Apache-2.0"}}, - {"MIT AND Apache-2.0 OR Zed", []string{"MIT", "Apache-2.0", "Zed"}}, - {"GPL-2.0-or-later", []string{"GPL-2.0-or-later"}}, - {"GPL-2.0+", []string{"GPL-2.0+"}}, + {"MIT--exact", []string{"MIT"}}, + {"mit--caseinsensitive", []string{"mit"}}, + {"mit--extra-space", []string{" MIT "}}, + {"Apache-2.0--active-early", []string{"Apache-2.0"}}, + {"Zed--active-end", []string{"Zed"}}, + {"MIT AND Apache-2.0--complex", []string{"MIT", "Apache-2.0"}}, + {"MIT AND Apache-2.0 OR Zed--complex", []string{"MIT", "Apache-2.0", "Zed"}}, + {"BSD-2-Clause-FreeBSD--deprecated", []string{"BSD-2-Clause-FreeBSD"}}, + {"GPL-2.0-or-later--range", []string{"GPL-2.0-or-later"}}, + {"Apache-1.0+--plus-range", []string{"Apache-1.0+"}}, + {"LicenseRef-scancode-adobe-postscript", []string{"LicenseRef-scancode-adobe-postscript"}}, + {"DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2", []string{"DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"}}, } func BenchmarkValidateLicenses(b *testing.B) { diff --git a/spdxexp/license.go b/spdxexp/license.go index a1800b0..be8e26c 100644 --- a/spdxexp/license.go +++ b/spdxexp/license.go @@ -8,7 +8,7 @@ import ( // activeLicense returns true if the id is an active license. func activeLicense(id string) (bool, string) { - return inLicenseList(spdxlicenses.GetLicenses(), id) + return spdxlicenses.IsActiveLicense(id) } // ActiveLicense returns true if the id is an active license. @@ -18,22 +18,12 @@ func ActiveLicense(id string) (bool, string) { // deprecatedLicense returns true if the id is a deprecated license. func deprecatedLicense(id string) (bool, string) { - return inLicenseList(spdxlicenses.GetDeprecated(), id) + return spdxlicenses.IsDeprecatedLicense(id) } // exceptionLicense returns true if the id is an exception license. func exceptionLicense(id string) (bool, string) { - return inLicenseList(spdxlicenses.GetExceptions(), id) -} - -// inLicenseList looks for id in the list of licenses. The check is case-insensitive (e.g. "mit" will match "MIT"). -func inLicenseList(licenses []string, id string) (bool, string) { - for _, license := range licenses { - if strings.EqualFold(license, id) { - return true, license - } - } - return false, id + return spdxlicenses.IsException(id) } const ( diff --git a/spdxexp/satisfies.go b/spdxexp/satisfies.go index f2bc9d6..5ad0d60 100644 --- a/spdxexp/satisfies.go +++ b/spdxexp/satisfies.go @@ -10,22 +10,108 @@ import ( // Returns true if all licenses are valid; otherwise, false. // Returns all the invalid licenses contained in the `licenses` argument. func ValidateLicenses(licenses []string) (bool, []string) { - // simple check for MIT covers the most common case and avoids the overhead of parsing for valid licenses - if len(licenses) == 1 && strings.EqualFold(licenses[0], "MIT") { - return true, []string{} - } + return ValidateLicensesWithOptions(licenses, ValidateLicensesOptions{}) +} - // if only one license, check for active license first since that is the next most common case - if len(licenses) == 1 { - if ok, _ := ActiveLicense(licenses[0]); ok { - return true, []string{} - } - } +// ValidateLicensesOptions controls how ValidateLicensesWithOptions validates input. +type ValidateLicensesOptions struct { + // FailComplexExpressions rejects SPDX license expressions (e.g. "MIT AND Apache-2.0"). + // Single license identifiers (including those with a WITH exception) are still allowed. + FailComplexExpressions bool + + // FailDeprecatedLicenses rejects deprecated SPDX license identifiers (e.g. "eCos-2.0"). + FailDeprecatedLicenses bool + + // FailAllLicenseRefs rejects all SPDX license references (e.g. "LicenseRef-MyLicense"). + FailAllLicenseRefs bool + + // FailAllDocumentRefs rejects all SPDX document references (e.g. "DocumentRef-MyDocument"). + FailAllDocumentRefs bool +} +// ValidateLicensesWithOptions checks if given licenses are valid according to SPDX. +// Returns true if all licenses are valid; otherwise, false. +// Returns all the invalid licenses contained in the `licenses` argument. +func ValidateLicensesWithOptions(licenses []string, options ValidateLicensesOptions) (bool, []string) { // handle all other cases with parsing, which will cover both single and multiple licenses and expressions valid := true invalidLicenses := []string{} for _, license := range licenses { + // MIT is the most common license, so check for it first before doing any processing to optimize for this case. + // By putting the isMIT check here, we can avoid the overhead of parsing for the most common case of MIT. + // Having it before trimming means that licenses with leading/trailing whitespace will not be validated + // as MIT by isMIT, but will still be correctly identified using activeLicense. As this is uncommon, it + // is an acceptable tradeoff to avoid the overhead of trimming for the more common case. + if isMIT(license) { + continue + } + + license = strings.TrimSpace(license) + + isAtomic := isAtomicLicense(license) + if isAtomic { + if ok, _ := activeLicense(license); ok { + continue + } + + if ok, _ := deprecatedLicense(license); ok { + if options.FailDeprecatedLicenses { + valid = false + invalidLicenses = append(invalidLicenses, license) + continue + } + // if FailDeprecatedLicenses is false, then consider the deprecated license valid and continue + continue + } + + if options.FailAllLicenseRefs { + if strings.HasPrefix(license, "LicenseRef-") { + valid = false + invalidLicenses = append(invalidLicenses, license) + continue + } + } + + if options.FailAllDocumentRefs { + if strings.HasPrefix(license, "DocumentRef-") { + valid = false + invalidLicenses = append(invalidLicenses, license) + continue + } + } + + // need to let this pass through to allow parsing LicenseRef and DocumentRef if either are allowed types + } + + if !isAtomic { + if hasException, licensePart, exceptionPart := isLicenseWithException(license); hasException { + // matches pattern "licensePart WITH exceptionPart", so validate both parts separately + if ok, _ := exceptionLicense(exceptionPart); ok { + if ok, _ := activeLicense(licensePart); ok { + continue + } + if !options.FailDeprecatedLicenses { + if ok, _ := deprecatedLicense(licensePart); ok { + continue + } + } + } + valid = false + invalidLicenses = append(invalidLicenses, license) + continue + } + } + + // all other non-atomic expressions are complex expressions with conjunctions (e.g. "MIT AND Apache-2.0"), + // so fail if complex expressions are not allowed + if options.FailComplexExpressions && !isAtomic { + valid = false + invalidLicenses = append(invalidLicenses, license) + continue + } + + // need to parse if allowing any of LicenseRef, DocumentRef, or complex expressions to be able to determine + // whether the license expression is valid if _, err := parse(license); err != nil { valid = false invalidLicenses = append(invalidLicenses, license) @@ -42,8 +128,12 @@ func Satisfies(testExpression string, allowedList []string) (bool, error) { return false, errors.New("allowedList requires at least one element, but is empty") } - // simple check for MIT covers the most common case and avoids the overhead of parsing the testExpression - if strings.EqualFold(testExpression, "MIT") { + // MIT is the most common license, so check for it first before doing any processing to optimize for this case. + // By putting the isMIT check here, we can avoid the overhead of parsing for the most common case of MIT. + // Having it before trimming means that licenses with leading/trailing whitespace will not be validated + // as MIT by isMIT, but will still be correctly identified using activeLicense. As this is uncommon, it + // is an acceptable tradeoff to avoid the overhead of trimming for the more common case. + if isMIT(testExpression) { for _, allowed := range allowedList { if strings.EqualFold(allowed, "MIT") { return true, nil @@ -52,9 +142,20 @@ func Satisfies(testExpression string, allowedList []string) (bool, error) { return false, nil } - // if only one license in the test expression, check for active license first to avoid the overhead of parsing - if !strings.Contains(testExpression, " ") { - if ok, _ := ActiveLicense(testExpression); ok { + testExpression = strings.TrimSpace(testExpression) + + if isAtomicLicense(testExpression) { + // if only one license in the test expression, check for active license to avoid the overhead of parsing + if ok, _ := activeLicense(testExpression); ok { + for _, allowed := range allowedList { + if strings.EqualFold(allowed, testExpression) { + return true, nil + } + } + } + + // if only one license in the test expression, check for deprecated license to avoid the overhead of parsing + if ok, _ := deprecatedLicense(testExpression); ok { for _, allowed := range allowedList { if strings.EqualFold(allowed, testExpression) { return true, nil @@ -63,6 +164,20 @@ func Satisfies(testExpression string, allowedList []string) (bool, error) { } } + // if test expression is a single license with exception, check it now to avoid the overhead of parsing + if hasException, licensePart, exceptionPart := isLicenseWithException(testExpression); hasException { + // matches pattern "licensePart WITH exceptionPart", so validate both parts separately + if ok, _ := activeLicense(licensePart); ok { + if ok, _ := exceptionLicense(exceptionPart); ok { + for _, allowed := range allowedList { + if strings.EqualFold(allowed, testExpression) { + return true, nil + } + } + } + } + } + // handle all other cases with parsing, which will cover both single and multiple licenses and expressions expressionNode, err := parse(testExpression) if err != nil { @@ -103,6 +218,33 @@ func stringsToNodes(licenseStrings []string) ([]*node, error) { return nodes, nil } +// isMIT checks if the test expression is MIT, ignoring case. +// NOTE: Caller should trim the test expression before calling this function to avoid false +// negatives (e.g. " MIT " would not match "MIT"). +func isMIT(testExpression string) bool { + return strings.EqualFold(testExpression, "MIT") +} + +// isAtomicLicense checks if the test expression is a single license identifier (e.g. "MIT"). +// NOTE: Caller should trim the test expression before calling this function to avoid false +// negatives (e.g. " MIT " would not be considered a single license). +func isAtomicLicense(testExpression string) bool { + return !strings.Contains(testExpression, " ") +} + +// isException checks if the test expression contains two licenses separated by WITH +// (e.g. "GPL-2.0-or-later WITH Bison-exception-2.2"). +// NOTE: Caller should trim the test expression before calling this function to avoid false +// negatives (e.g. " MIT " would not be considered a single license). +func isLicenseWithException(testExpression string) (bool, string, string) { + // split by " " and check if there are exactly 3 parts and the middle part is "WITH" + parts := strings.Fields(testExpression) + if len(parts) == 3 && strings.EqualFold(parts[1], "WITH") { + return true, parts[0], parts[2] + } + return false, "", "" +} + // isCompatible checks if expressionPart is compatible with allowed list. // Expression part is an array of licenses that are ANDed together. // Allowed is an array of licenses that can fulfill the expression. diff --git a/spdxexp/satisfies_test.go b/spdxexp/satisfies_test.go index e07fa80..cae9fce 100644 --- a/spdxexp/satisfies_test.go +++ b/spdxexp/satisfies_test.go @@ -41,6 +41,193 @@ func TestValidateLicenses(t *testing.T) { } } +func TestValidateLicensesWithOptions_FailComplexExpressions(t *testing.T) { + tests := []struct { + name string + inputLicenses []string + options ValidateLicensesOptions + allValid bool + invalidLicenses []string + }{ + { + name: "Expressions rejected", + inputLicenses: []string{"MIT AND Apache-2.0"}, + options: ValidateLicensesOptions{FailComplexExpressions: true}, + allValid: false, + invalidLicenses: []string{ + "MIT AND Apache-2.0", + }, + }, + { + name: "Mixed list rejects only expressions", + inputLicenses: []string{"MIT", "Apache-2.0", "LGPL-2.1-only OR MIT"}, + options: ValidateLicensesOptions{FailComplexExpressions: true}, + allValid: false, + invalidLicenses: []string{ + "LGPL-2.1-only OR MIT", + }, + }, + { + name: "WITH exception is not treated as complex expression", + inputLicenses: []string{"GPL-2.0-or-later WITH Bison-exception-2.2"}, + options: ValidateLicensesOptions{FailComplexExpressions: true}, + allValid: true, + invalidLicenses: []string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + valid, invalidLicenses := ValidateLicensesWithOptions(test.inputLicenses, test.options) + assert.EqualValues(t, test.invalidLicenses, invalidLicenses) + assert.Equal(t, test.allValid, valid) + }) + } +} + +func TestValidateLicensesWithOptions_FailDeprecatedLicenses(t *testing.T) { + // eCos-2.0 is a known deprecated SPDX license ID (see TestDeprecatedLicense). + deprecatedLicense := "eCos-2.0" + + tests := []struct { + name string + inputLicenses []string + options ValidateLicensesOptions + allValid bool + invalidLicenses []string + }{ + { + name: "Deprecated license rejected", + inputLicenses: []string{deprecatedLicense}, + options: ValidateLicensesOptions{FailDeprecatedLicenses: true}, + allValid: false, + invalidLicenses: []string{ + deprecatedLicense, + }, + }, + { + name: "Mixed list rejects only deprecated licenses", + inputLicenses: []string{"MIT", "Apache-2.0", deprecatedLicense}, + options: ValidateLicensesOptions{FailDeprecatedLicenses: true}, + allValid: false, + invalidLicenses: []string{ + deprecatedLicense, + }, + }, + { + name: "WITH exception rejects deprecated license if FailDeprecatedLicenses is true", + inputLicenses: []string{deprecatedLicense + " WITH Bison-exception-2.2"}, + options: ValidateLicensesOptions{FailDeprecatedLicenses: true}, + allValid: false, + invalidLicenses: []string{ + deprecatedLicense + " WITH Bison-exception-2.2", + }, + }, + { + name: "WITH exception allows deprecated license if FailDeprecatedLicenses is false", + inputLicenses: []string{deprecatedLicense + " WITH Bison-exception-2.2"}, + options: ValidateLicensesOptions{FailDeprecatedLicenses: false}, + allValid: true, + invalidLicenses: []string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + valid, invalidLicenses := ValidateLicensesWithOptions(test.inputLicenses, test.options) + assert.EqualValues(t, test.invalidLicenses, invalidLicenses) + assert.Equal(t, test.allValid, valid) + }) + } +} + +func TestValidateLicensesWithOptions_AllOptions(t *testing.T) { + documentRef := "DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2" + licenseRef := "LicenseRef-MIT-Style-1" + deprecated := "eCos-2.0" + expression := "MIT AND Apache-2.0" + licenseWithException := "GPL-2.0-or-later WITH Bison-exception-2.2" + + tests := []struct { + name string + licenses []string + options ValidateLicensesOptions + valid bool + invalidLicenses []string + }{ + { + name: "Defaults allow deprecated, refs, and expressions", + licenses: []string{" MIT ", deprecated, licenseRef, documentRef, expression, licenseWithException}, + options: ValidateLicensesOptions{}, + valid: true, + invalidLicenses: []string{}, + }, + { + name: "FailDeprecatedLicenses rejects deprecated IDs", + licenses: []string{deprecated, "Apache-2.0"}, + options: ValidateLicensesOptions{FailDeprecatedLicenses: true}, + valid: false, + invalidLicenses: []string{deprecated}, + }, + { + name: "FailComplexExpressions rejects conjunction expressions", + licenses: []string{expression, licenseWithException}, + options: ValidateLicensesOptions{FailComplexExpressions: true}, + valid: false, + invalidLicenses: []string{expression}, + }, + { + name: "FailComplexExpressions does not duplicate invalid entries", + licenses: []string{"MIT AND APCHE-2.0"}, + options: ValidateLicensesOptions{FailComplexExpressions: true}, + valid: false, + invalidLicenses: []string{"MIT AND APCHE-2.0"}, + }, + { + name: "FailAllLicenseRefs rejects LicenseRef but allows DocumentRef", + licenses: []string{licenseRef, documentRef}, + options: ValidateLicensesOptions{FailAllLicenseRefs: true}, + valid: false, + invalidLicenses: []string{licenseRef}, + }, + { + name: "FailAllDocumentRefs rejects DocumentRef but allows LicenseRef", + licenses: []string{documentRef, licenseRef}, + options: ValidateLicensesOptions{FailAllDocumentRefs: true}, + valid: false, + invalidLicenses: []string{documentRef}, + }, + { + name: "FailAllLicenseRefs and FailAllDocumentRefs rejects any non-active atomic ref", + licenses: []string{licenseRef, documentRef, "CustomRef-foo"}, + options: ValidateLicensesOptions{FailAllLicenseRefs: true, FailAllDocumentRefs: true}, + valid: false, + invalidLicenses: []string{licenseRef, documentRef, "CustomRef-foo"}, + }, + { + name: "All flags together", + licenses: []string{deprecated, licenseRef, documentRef, expression, licenseWithException, "Apache-2.0"}, + options: ValidateLicensesOptions{ + FailComplexExpressions: true, + FailDeprecatedLicenses: true, + FailAllLicenseRefs: true, + FailAllDocumentRefs: true, + }, + valid: false, + invalidLicenses: []string{deprecated, licenseRef, documentRef, expression}, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + valid, invalidLicenses := ValidateLicensesWithOptions(test.licenses, test.options) + assert.Equal(t, test.valid, valid) + assert.EqualValues(t, test.invalidLicenses, invalidLicenses) + }) + } +} + // TestSatisfiesSingle lets you quickly test a single call to Satisfies with a specific license expression and allowed list of licenses. // To test a different expression, change the expression, allowed licenses, and expected result in the function body. // TO RUN: go test ./expression -run TestSatisfiesSingle @@ -56,6 +243,81 @@ func TestSatisfiesSingle(t *testing.T) { assert.Equal(t, expectedResult, actualResult) } +func TestSatisfies_FastPathValidation(t *testing.T) { + tests := []struct { + name string + repoExpression string + allowedList []string + satisfied bool + expectErr bool + expectedErr string + }{ + { + name: "MIT trims whitespace", + repoExpression: " MIT \t", + allowedList: []string{"MIT"}, + satisfied: true, + }, + { + name: "Atomic deprecated license matches allowed list", + repoExpression: " eCos-2.0 ", + allowedList: []string{"ECOS-2.0"}, + satisfied: true, + }, + { + name: "Atomic deprecated license not in allowed list", + repoExpression: "eCos-2.0", + allowedList: []string{"MIT"}, + satisfied: false, + }, + { + name: "Single license WITH exception exact match", + repoExpression: "GPL-2.0-or-later WITH Bison-exception-2.2", + allowedList: []string{"GPL-2.0-or-later WITH Bison-exception-2.2"}, + satisfied: true, + }, + { + name: "Single license WITH exception not in allowed list", + repoExpression: "GPL-2.0-or-later WITH Bison-exception-2.2", + allowedList: []string{"MIT"}, + satisfied: false, + }, + { + name: "Single license WITH exception matches allow list ignoring case", + repoExpression: "gpl-2.0-or-later with bison-exception-2.2", + allowedList: []string{"GPL-2.0-or-later WITH Bison-exception-2.2"}, + satisfied: true, + }, + { + name: "Single license WITH invalid exception returns error", + repoExpression: "GPL-2.0-or-later WITH NOT-A-REAL-EXCEPTION", + allowedList: []string{"GPL-2.0-or-later WITH NOT-A-REAL-EXCEPTION"}, + expectErr: true, + expectedErr: "unknown license 'NOT-A-REAL-EXCEPTION' at offset 22", + }, + { + name: "Single license WITH invalid license part returns error", + repoExpression: "NOT-A-REAL-LICENSE WITH Bison-exception-2.2", + allowedList: []string{"NOT-A-REAL-LICENSE WITH Bison-exception-2.2"}, + expectErr: true, + expectedErr: "unknown license 'NOT-A-REAL-LICENSE' at offset 0", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + actualResult, err := Satisfies(test.repoExpression, test.allowedList) + if test.expectErr { + assert.EqualError(t, err, test.expectedErr) + return + } + assert.NoError(t, err) + assert.Equal(t, test.satisfied, actualResult) + }) + } +} + func TestSatisfies(t *testing.T) { tests := []struct { name string @@ -66,7 +328,6 @@ func TestSatisfies(t *testing.T) { }{ // TODO: Test error conditions (e.g. GPL is an invalid license, Apachie + has invalid + operator) // regression tests from spdx-satisfies.js - comments for satisfies function - // TODO: Commented out tests are not yet supported. {"MIT satisfies [MIT]", "MIT", []string{"MIT"}, true, nil}, {"miT satisfies [MIT]", "miT", []string{"MIT"}, true, nil}, {"MIT satisfies [mit]", "MIT", []string{"mit"}, true, nil}, @@ -109,7 +370,6 @@ func TestSatisfies(t *testing.T) { {"! Apache-3.0 satisfies [Apache-2.0-only]", "Apache-3.0", []string{"Apache-2.0-only"}, false, errors.New("unknown license 'Apache-3.0' at offset 0")}, // regression tests from spdx-satisfies.js - assert statements in README - // TODO: Commented out tests are not yet supported. {"MIT satisfies [MIT]", "MIT", []string{"MIT"}, true, nil}, {"MIT satisfies [ISC, MIT]", "MIT", []string{"ISC", "MIT"}, true, nil}, @@ -319,7 +579,51 @@ func TestExpandOr(t *testing.T) { // lic: nil, // ref: nil, // }, - // [][]string{{"MIT", "Apache-1.0+"}, {"DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"}, {"GPL-2.0 with Bison-exception-2.2"}}}, + // // [][]string{{"MIT", "Apache-1.0+"}, {"DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"}, {"GPL-2.0 with Bison-exception-2.2"}}}, + // [][]*node{ + // { + // { + // role: licenseNode, + // exp: nil, + // lic: &licenseNodePartial{ + // license: "MIT", hasPlus: false, + // hasException: false, exception: ""}, + // ref: nil, + // }, + // { + // role: licenseNode, + // exp: nil, + // lic: &licenseNodePartial{ + // license: "Apache-1.0", hasPlus: true, + // hasException: false, exception: ""}, + // ref: nil, + // }, + // }, + // { + // { + // role: licenseRefNode, + // exp: nil, + // lic: nil, + // ref: &referenceNodePartial{ + // hasDocumentRef: true, + // documentRef: "spdx-tool-1.2", + // licenseRef: "MIT-Style-2", + // }, + // }, + // { + // role: licenseNode, + // exp: nil, + // lic: &licenseNodePartial{ + // license: "GPL-2.0", + // hasPlus: false, + // hasException: true, + // exception: "Bison-exception-2.2", + // }, + // ref: nil, + // }, + // }, + // }, + // }, } for _, test := range tests { @@ -2414,3 +2718,17 @@ func ExampleValidateLicenses_allBad() { fmt.Println(ValidateLicenses([]string{"MTI", "Apache--2.0", "GPL"})) // Output: false [MTI Apache--2.0 GPL] } + +// TestValidateLicenses_BenchmarkExamples is a safety check to ensure benchmark improvements are not due +// to changes in behavior of the ValidateLicenses function. +func TestValidateLicenses_BenchmarkExamples(t *testing.T) { + // This test is used to verify that the test expressions used in the benchmarks return expected results. + // If any of the test expressions are invalid, then there is likely an issue with the benchmark results and investigation would be needed. + for _, test := range validateLicensesBenchmarkScenarios { + t.Run(test.name, func(t *testing.T) { + valid, invalidLicenses := ValidateLicenses(test.testLicenses) + assert.True(t, valid, "Expected licenses to be valid for scenario: %s", test.name) + assert.Empty(t, invalidLicenses, "Expected no invalid licenses for scenario: %s", test.name) + }) + } +} diff --git a/spdxexp/spdxlicenses/get_deprecated.go b/spdxexp/spdxlicenses/get_deprecated.go index c2cbf3f..0f0d386 100644 --- a/spdxexp/spdxlicenses/get_deprecated.go +++ b/spdxexp/spdxlicenses/get_deprecated.go @@ -3,6 +3,27 @@ package spdxlicenses // Code generated by go-spdx cmd/license.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsDeprecatedLicense does a case-insensitive lookup for the license id in the deprecated licenses map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsDeprecatedLicense(id string) (bool, string) { + foundID, ok := deprecatedMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetDeprecatedMap returns a map of deprecated license IDs keyed by uppercase ID. +func GetDeprecatedMap() map[string]string { + copied := make(map[string]string, len(deprecatedMap)) + for k, v := range deprecatedMap { + copied[k] = v + } + return copied +} + // GetDeprecated returns a slice of deprecated license IDs. func GetDeprecated() []string { return []string{ @@ -40,3 +61,38 @@ func GetDeprecated() []string { "wxWindows", } } + +var deprecatedMap = map[string]string{ + "AGPL-1.0": "AGPL-1.0", + "AGPL-3.0": "AGPL-3.0", + "BSD-2-CLAUSE-FREEBSD": "BSD-2-Clause-FreeBSD", + "BSD-2-CLAUSE-NETBSD": "BSD-2-Clause-NetBSD", + "BZIP2-1.0.5": "bzip2-1.0.5", + "ECOS-2.0": "eCos-2.0", + "GFDL-1.1": "GFDL-1.1", + "GFDL-1.2": "GFDL-1.2", + "GFDL-1.3": "GFDL-1.3", + "GPL-1.0": "GPL-1.0", + "GPL-1.0+": "GPL-1.0+", + "GPL-2.0": "GPL-2.0", + "GPL-2.0+": "GPL-2.0+", + "GPL-2.0-WITH-AUTOCONF-EXCEPTION": "GPL-2.0-with-autoconf-exception", + "GPL-2.0-WITH-BISON-EXCEPTION": "GPL-2.0-with-bison-exception", + "GPL-2.0-WITH-CLASSPATH-EXCEPTION": "GPL-2.0-with-classpath-exception", + "GPL-2.0-WITH-FONT-EXCEPTION": "GPL-2.0-with-font-exception", + "GPL-2.0-WITH-GCC-EXCEPTION": "GPL-2.0-with-GCC-exception", + "GPL-3.0": "GPL-3.0", + "GPL-3.0+": "GPL-3.0+", + "GPL-3.0-WITH-AUTOCONF-EXCEPTION": "GPL-3.0-with-autoconf-exception", + "GPL-3.0-WITH-GCC-EXCEPTION": "GPL-3.0-with-GCC-exception", + "LGPL-2.0": "LGPL-2.0", + "LGPL-2.0+": "LGPL-2.0+", + "LGPL-2.1": "LGPL-2.1", + "LGPL-2.1+": "LGPL-2.1+", + "LGPL-3.0": "LGPL-3.0", + "LGPL-3.0+": "LGPL-3.0+", + "NET-SNMP": "Net-SNMP", + "NUNIT": "Nunit", + "STANDARDML-NJ": "StandardML-NJ", + "WXWINDOWS": "wxWindows", +} diff --git a/spdxexp/spdxlicenses/get_exceptions.go b/spdxexp/spdxlicenses/get_exceptions.go index 80a43b1..2a580d9 100644 --- a/spdxexp/spdxlicenses/get_exceptions.go +++ b/spdxexp/spdxlicenses/get_exceptions.go @@ -3,6 +3,27 @@ package spdxlicenses // Code generated by go-spdx cmd/exceptions.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsException does a case-insensitive lookup for the exception id in the exceptions map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsException(id string) (bool, string) { + foundID, ok := exceptionsMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetExceptionsMap returns a map of exception license IDs keyed by uppercase ID. +func GetExceptionsMap() map[string]string { + copied := make(map[string]string, len(exceptionsMap)) + for k, v := range exceptionsMap { + copied[k] = v + } + return copied +} + // GetExceptions returns a slice of exception license IDs. func GetExceptions() []string { return []string{ @@ -91,3 +112,89 @@ func GetExceptions() []string { "x11vnc-openssl-exception", } } + +var exceptionsMap = map[string]string{ + "389-EXCEPTION": "389-exception", + "ASTERISK-EXCEPTION": "Asterisk-exception", + "ASTERISK-LINKING-PROTOCOLS-EXCEPTION": "Asterisk-linking-protocols-exception", + "AUTOCONF-EXCEPTION-2.0": "Autoconf-exception-2.0", + "AUTOCONF-EXCEPTION-3.0": "Autoconf-exception-3.0", + "AUTOCONF-EXCEPTION-GENERIC": "Autoconf-exception-generic", + "AUTOCONF-EXCEPTION-GENERIC-3.0": "Autoconf-exception-generic-3.0", + "AUTOCONF-EXCEPTION-MACRO": "Autoconf-exception-macro", + "BISON-EXCEPTION-1.24": "Bison-exception-1.24", + "BISON-EXCEPTION-2.2": "Bison-exception-2.2", + "BOOTLOADER-EXCEPTION": "Bootloader-exception", + "CGAL-LINKING-EXCEPTION": "CGAL-linking-exception", + "CLASSPATH-EXCEPTION-2.0": "Classpath-exception-2.0", + "CLASSPATH-EXCEPTION-2.0-SHORT": "Classpath-exception-2.0-short", + "CLISP-EXCEPTION-2.0": "CLISP-exception-2.0", + "CRYPTSETUP-OPENSSL-EXCEPTION": "cryptsetup-OpenSSL-exception", + "DIGIA-QT-LGPL-EXCEPTION-1.1": "Digia-Qt-LGPL-exception-1.1", + "DIGIRULE-FOSS-EXCEPTION": "DigiRule-FOSS-exception", + "ECOS-EXCEPTION-2.0": "eCos-exception-2.0", + "ERLANG-OTP-LINKING-EXCEPTION": "erlang-otp-linking-exception", + "FAWKES-RUNTIME-EXCEPTION": "Fawkes-Runtime-exception", + "FLTK-EXCEPTION": "FLTK-exception", + "FMT-EXCEPTION": "fmt-exception", + "FONT-EXCEPTION-2.0": "Font-exception-2.0", + "FREERTOS-EXCEPTION-2.0": "freertos-exception-2.0", + "GCC-EXCEPTION-2.0": "GCC-exception-2.0", + "GCC-EXCEPTION-2.0-NOTE": "GCC-exception-2.0-note", + "GCC-EXCEPTION-3.1": "GCC-exception-3.1", + "GMSH-EXCEPTION": "Gmsh-exception", + "GNAT-EXCEPTION": "GNAT-exception", + "GNOME-EXAMPLES-EXCEPTION": "GNOME-examples-exception", + "GNU-COMPILER-EXCEPTION": "GNU-compiler-exception", + "GNU-JAVAMAIL-EXCEPTION": "gnu-javamail-exception", + "GPL-3.0-389-DS-BASE-EXCEPTION": "GPL-3.0-389-ds-base-exception", + "GPL-3.0-INTERFACE-EXCEPTION": "GPL-3.0-interface-exception", + "GPL-3.0-LINKING-EXCEPTION": "GPL-3.0-linking-exception", + "GPL-3.0-LINKING-SOURCE-EXCEPTION": "GPL-3.0-linking-source-exception", + "GPL-CC-1.0": "GPL-CC-1.0", + "GSTREAMER-EXCEPTION-2005": "GStreamer-exception-2005", + "GSTREAMER-EXCEPTION-2008": "GStreamer-exception-2008", + "HARBOUR-EXCEPTION": "harbour-exception", + "I2P-GPL-JAVA-EXCEPTION": "i2p-gpl-java-exception", + "INDEPENDENT-MODULES-EXCEPTION": "Independent-modules-exception", + "KICAD-LIBRARIES-EXCEPTION": "KiCad-libraries-exception", + "KVIRC-OPENSSL-EXCEPTION": "kvirc-openssl-exception", + "LGPL-3.0-LINKING-EXCEPTION": "LGPL-3.0-linking-exception", + "LIBPRI-OPENH323-EXCEPTION": "libpri-OpenH323-exception", + "LIBTOOL-EXCEPTION": "Libtool-exception", + "LINUX-SYSCALL-NOTE": "Linux-syscall-note", + "LLGPL": "LLGPL", + "LLVM-EXCEPTION": "LLVM-exception", + "LZMA-EXCEPTION": "LZMA-exception", + "MIF-EXCEPTION": "mif-exception", + "MXML-EXCEPTION": "mxml-exception", + "OCAML-LGPL-LINKING-EXCEPTION": "OCaml-LGPL-linking-exception", + "OCCT-EXCEPTION-1.0": "OCCT-exception-1.0", + "OPENJDK-ASSEMBLY-EXCEPTION-1.0": "OpenJDK-assembly-exception-1.0", + "OPENVPN-OPENSSL-EXCEPTION": "openvpn-openssl-exception", + "PCRE2-EXCEPTION": "PCRE2-exception", + "POLYPARSE-EXCEPTION": "polyparse-exception", + "PS-OR-PDF-FONT-EXCEPTION-20170817": "PS-or-PDF-font-exception-20170817", + "QPL-1.0-INRIA-2004-EXCEPTION": "QPL-1.0-INRIA-2004-exception", + "QT-GPL-EXCEPTION-1.0": "Qt-GPL-exception-1.0", + "QT-LGPL-EXCEPTION-1.1": "Qt-LGPL-exception-1.1", + "QWT-EXCEPTION-1.0": "Qwt-exception-1.0", + "ROMIC-EXCEPTION": "romic-exception", + "RRDTOOL-FLOSS-EXCEPTION-2.0": "RRDtool-FLOSS-exception-2.0", + "RSYNC-LINKING-EXCEPTION": "rsync-linking-exception", + "SANE-EXCEPTION": "SANE-exception", + "SHL-2.0": "SHL-2.0", + "SHL-2.1": "SHL-2.1", + "SIMPLE-LIBRARY-USAGE-EXCEPTION": "Simple-Library-Usage-exception", + "SQLITESTUDIO-OPENSSL-EXCEPTION": "sqlitestudio-OpenSSL-exception", + "STUNNEL-EXCEPTION": "stunnel-exception", + "SWI-EXCEPTION": "SWI-exception", + "SWIFT-EXCEPTION": "Swift-exception", + "TEXINFO-EXCEPTION": "Texinfo-exception", + "U-BOOT-EXCEPTION-2.0": "u-boot-exception-2.0", + "UBDL-EXCEPTION": "UBDL-exception", + "UNIVERSAL-FOSS-EXCEPTION-1.0": "Universal-FOSS-exception-1.0", + "VSFTPD-OPENSSL-EXCEPTION": "vsftpd-openssl-exception", + "WXWINDOWS-EXCEPTION-3.1": "WxWindows-exception-3.1", + "X11VNC-OPENSSL-EXCEPTION": "x11vnc-openssl-exception", +} diff --git a/spdxexp/spdxlicenses/get_licenses.go b/spdxexp/spdxlicenses/get_licenses.go index 861e7f0..d885f1e 100644 --- a/spdxexp/spdxlicenses/get_licenses.go +++ b/spdxexp/spdxlicenses/get_licenses.go @@ -3,6 +3,27 @@ package spdxlicenses // Code generated by go-spdx cmd/license.go. DO NOT EDIT. // Source: https://github.com/spdx/license-list-data specifies official SPDX license list. +import "strings" + +// IsActiveLicense does a case-insensitive lookup for the license id in the active licenses map. +// It returns true and the case-sensitive ID if found, otherwise false and the original id. +func IsActiveLicense(id string) (bool, string) { + foundID, ok := licensesMap[strings.ToUpper(id)] + if ok { + return true, foundID + } + return false, id +} + +// GetLicensesMap returns a map of active license IDs keyed by uppercase ID. +func GetLicensesMap() map[string]string { + copied := make(map[string]string, len(licensesMap)) + for k, v := range licensesMap { + copied[k] = v + } + return copied +} + // GetLicenses returns a slice of active license IDs. func GetLicenses() []string { return []string{ @@ -703,3 +724,701 @@ func GetLicenses() []string { "ZPL-2.1", } } + +var licensesMap = map[string]string{ + "0BSD": "0BSD", + "3D-SLICER-1.0": "3D-Slicer-1.0", + "AAL": "AAL", + "ABSTYLES": "Abstyles", + "ADACORE-DOC": "AdaCore-doc", + "ADOBE-2006": "Adobe-2006", + "ADOBE-DISPLAY-POSTSCRIPT": "Adobe-Display-PostScript", + "ADOBE-GLYPH": "Adobe-Glyph", + "ADOBE-UTOPIA": "Adobe-Utopia", + "ADSL": "ADSL", + "ADVANCED-CRYPTICS-DICTIONARY": "Advanced-Cryptics-Dictionary", + "AFL-1.1": "AFL-1.1", + "AFL-1.2": "AFL-1.2", + "AFL-2.0": "AFL-2.0", + "AFL-2.1": "AFL-2.1", + "AFL-3.0": "AFL-3.0", + "AFMPARSE": "Afmparse", + "AGPL-1.0-ONLY": "AGPL-1.0-only", + "AGPL-1.0-OR-LATER": "AGPL-1.0-or-later", + "AGPL-3.0-ONLY": "AGPL-3.0-only", + "AGPL-3.0-OR-LATER": "AGPL-3.0-or-later", + "ALADDIN": "Aladdin", + "ALGLIB-DOCUMENTATION": "ALGLIB-Documentation", + "AMD-NEWLIB": "AMD-newlib", + "AMDPLPA": "AMDPLPA", + "AML": "AML", + "AML-GLSLANG": "AML-glslang", + "AMPAS": "AMPAS", + "ANTLR-PD": "ANTLR-PD", + "ANTLR-PD-FALLBACK": "ANTLR-PD-fallback", + "ANY-OSI": "any-OSI", + "ANY-OSI-PERL-MODULES": "any-OSI-perl-modules", + "APACHE-1.0": "Apache-1.0", + "APACHE-1.1": "Apache-1.1", + "APACHE-2.0": "Apache-2.0", + "APAFML": "APAFML", + "APL-1.0": "APL-1.0", + "APP-S2P": "App-s2p", + "APSL-1.0": "APSL-1.0", + "APSL-1.1": "APSL-1.1", + "APSL-1.2": "APSL-1.2", + "APSL-2.0": "APSL-2.0", + "ARPHIC-1999": "Arphic-1999", + "ARTISTIC-1.0": "Artistic-1.0", + "ARTISTIC-1.0-CL8": "Artistic-1.0-cl8", + "ARTISTIC-1.0-PERL": "Artistic-1.0-Perl", + "ARTISTIC-2.0": "Artistic-2.0", + "ARTISTIC-DIST": "Artistic-dist", + "ASPELL-RU": "Aspell-RU", + "ASWF-DIGITAL-ASSETS-1.0": "ASWF-Digital-Assets-1.0", + "ASWF-DIGITAL-ASSETS-1.1": "ASWF-Digital-Assets-1.1", + "BAEKMUK": "Baekmuk", + "BAHYPH": "Bahyph", + "BARR": "Barr", + "BCRYPT-SOLAR-DESIGNER": "bcrypt-Solar-Designer", + "BEERWARE": "Beerware", + "BITSTREAM-CHARTER": "Bitstream-Charter", + "BITSTREAM-VERA": "Bitstream-Vera", + "BITTORRENT-1.0": "BitTorrent-1.0", + "BITTORRENT-1.1": "BitTorrent-1.1", + "BLESSING": "blessing", + "BLUEOAK-1.0.0": "BlueOak-1.0.0", + "BOEHM-GC": "Boehm-GC", + "BOEHM-GC-WITHOUT-FEE": "Boehm-GC-without-fee", + "BOLA-1.1": "BOLA-1.1", + "BORCEUX": "Borceux", + "BRIAN-GLADMAN-2-CLAUSE": "Brian-Gladman-2-Clause", + "BRIAN-GLADMAN-3-CLAUSE": "Brian-Gladman-3-Clause", + "BSD-1-CLAUSE": "BSD-1-Clause", + "BSD-2-CLAUSE": "BSD-2-Clause", + "BSD-2-CLAUSE-DARWIN": "BSD-2-Clause-Darwin", + "BSD-2-CLAUSE-FIRST-LINES": "BSD-2-Clause-first-lines", + "BSD-2-CLAUSE-PATENT": "BSD-2-Clause-Patent", + "BSD-2-CLAUSE-PKGCONF-DISCLAIMER": "BSD-2-Clause-pkgconf-disclaimer", + "BSD-2-CLAUSE-VIEWS": "BSD-2-Clause-Views", + "BSD-3-CLAUSE": "BSD-3-Clause", + "BSD-3-CLAUSE-ACPICA": "BSD-3-Clause-acpica", + "BSD-3-CLAUSE-ATTRIBUTION": "BSD-3-Clause-Attribution", + "BSD-3-CLAUSE-CLEAR": "BSD-3-Clause-Clear", + "BSD-3-CLAUSE-FLEX": "BSD-3-Clause-flex", + "BSD-3-CLAUSE-HP": "BSD-3-Clause-HP", + "BSD-3-CLAUSE-LBNL": "BSD-3-Clause-LBNL", + "BSD-3-CLAUSE-MODIFICATION": "BSD-3-Clause-Modification", + "BSD-3-CLAUSE-NO-MILITARY-LICENSE": "BSD-3-Clause-No-Military-License", + "BSD-3-CLAUSE-NO-NUCLEAR-LICENSE": "BSD-3-Clause-No-Nuclear-License", + "BSD-3-CLAUSE-NO-NUCLEAR-LICENSE-2014": "BSD-3-Clause-No-Nuclear-License-2014", + "BSD-3-CLAUSE-NO-NUCLEAR-WARRANTY": "BSD-3-Clause-No-Nuclear-Warranty", + "BSD-3-CLAUSE-OPEN-MPI": "BSD-3-Clause-Open-MPI", + "BSD-3-CLAUSE-SUN": "BSD-3-Clause-Sun", + "BSD-3-CLAUSE-TSO": "BSD-3-Clause-Tso", + "BSD-4-CLAUSE": "BSD-4-Clause", + "BSD-4-CLAUSE-SHORTENED": "BSD-4-Clause-Shortened", + "BSD-4-CLAUSE-UC": "BSD-4-Clause-UC", + "BSD-4.3RENO": "BSD-4.3RENO", + "BSD-4.3TAHOE": "BSD-4.3TAHOE", + "BSD-ADVERTISING-ACKNOWLEDGEMENT": "BSD-Advertising-Acknowledgement", + "BSD-ATTRIBUTION-HPND-DISCLAIMER": "BSD-Attribution-HPND-disclaimer", + "BSD-INFERNO-NETTVERK": "BSD-Inferno-Nettverk", + "BSD-MARK-MODIFICATIONS": "BSD-Mark-Modifications", + "BSD-PROTECTION": "BSD-Protection", + "BSD-SOURCE-BEGINNING-FILE": "BSD-Source-beginning-file", + "BSD-SOURCE-CODE": "BSD-Source-Code", + "BSD-SYSTEMICS": "BSD-Systemics", + "BSD-SYSTEMICS-W3WORKS": "BSD-Systemics-W3Works", + "BSL-1.0": "BSL-1.0", + "BUDDY": "Buddy", + "BUSL-1.1": "BUSL-1.1", + "BZIP2-1.0.6": "bzip2-1.0.6", + "C-UDA-1.0": "C-UDA-1.0", + "CAL-1.0": "CAL-1.0", + "CAL-1.0-COMBINED-WORK-EXCEPTION": "CAL-1.0-Combined-Work-Exception", + "CALDERA": "Caldera", + "CALDERA-NO-PREAMBLE": "Caldera-no-preamble", + "CAPEC-TOU": "CAPEC-tou", + "CATHARON": "Catharon", + "CATOSL-1.1": "CATOSL-1.1", + "CC-BY-1.0": "CC-BY-1.0", + "CC-BY-2.0": "CC-BY-2.0", + "CC-BY-2.5": "CC-BY-2.5", + "CC-BY-2.5-AU": "CC-BY-2.5-AU", + "CC-BY-3.0": "CC-BY-3.0", + "CC-BY-3.0-AT": "CC-BY-3.0-AT", + "CC-BY-3.0-AU": "CC-BY-3.0-AU", + "CC-BY-3.0-DE": "CC-BY-3.0-DE", + "CC-BY-3.0-IGO": "CC-BY-3.0-IGO", + "CC-BY-3.0-NL": "CC-BY-3.0-NL", + "CC-BY-3.0-US": "CC-BY-3.0-US", + "CC-BY-4.0": "CC-BY-4.0", + "CC-BY-NC-1.0": "CC-BY-NC-1.0", + "CC-BY-NC-2.0": "CC-BY-NC-2.0", + "CC-BY-NC-2.5": "CC-BY-NC-2.5", + "CC-BY-NC-3.0": "CC-BY-NC-3.0", + "CC-BY-NC-3.0-DE": "CC-BY-NC-3.0-DE", + "CC-BY-NC-4.0": "CC-BY-NC-4.0", + "CC-BY-NC-ND-1.0": "CC-BY-NC-ND-1.0", + "CC-BY-NC-ND-2.0": "CC-BY-NC-ND-2.0", + "CC-BY-NC-ND-2.5": "CC-BY-NC-ND-2.5", + "CC-BY-NC-ND-3.0": "CC-BY-NC-ND-3.0", + "CC-BY-NC-ND-3.0-DE": "CC-BY-NC-ND-3.0-DE", + "CC-BY-NC-ND-3.0-IGO": "CC-BY-NC-ND-3.0-IGO", + "CC-BY-NC-ND-4.0": "CC-BY-NC-ND-4.0", + "CC-BY-NC-SA-1.0": "CC-BY-NC-SA-1.0", + "CC-BY-NC-SA-2.0": "CC-BY-NC-SA-2.0", + "CC-BY-NC-SA-2.0-DE": "CC-BY-NC-SA-2.0-DE", + "CC-BY-NC-SA-2.0-FR": "CC-BY-NC-SA-2.0-FR", + "CC-BY-NC-SA-2.0-UK": "CC-BY-NC-SA-2.0-UK", + "CC-BY-NC-SA-2.5": "CC-BY-NC-SA-2.5", + "CC-BY-NC-SA-3.0": "CC-BY-NC-SA-3.0", + "CC-BY-NC-SA-3.0-DE": "CC-BY-NC-SA-3.0-DE", + "CC-BY-NC-SA-3.0-IGO": "CC-BY-NC-SA-3.0-IGO", + "CC-BY-NC-SA-4.0": "CC-BY-NC-SA-4.0", + "CC-BY-ND-1.0": "CC-BY-ND-1.0", + "CC-BY-ND-2.0": "CC-BY-ND-2.0", + "CC-BY-ND-2.5": "CC-BY-ND-2.5", + "CC-BY-ND-3.0": "CC-BY-ND-3.0", + "CC-BY-ND-3.0-DE": "CC-BY-ND-3.0-DE", + "CC-BY-ND-4.0": "CC-BY-ND-4.0", + "CC-BY-SA-1.0": "CC-BY-SA-1.0", + "CC-BY-SA-2.0": "CC-BY-SA-2.0", + "CC-BY-SA-2.0-UK": "CC-BY-SA-2.0-UK", + "CC-BY-SA-2.1-JP": "CC-BY-SA-2.1-JP", + "CC-BY-SA-2.5": "CC-BY-SA-2.5", + "CC-BY-SA-3.0": "CC-BY-SA-3.0", + "CC-BY-SA-3.0-AT": "CC-BY-SA-3.0-AT", + "CC-BY-SA-3.0-DE": "CC-BY-SA-3.0-DE", + "CC-BY-SA-3.0-IGO": "CC-BY-SA-3.0-IGO", + "CC-BY-SA-4.0": "CC-BY-SA-4.0", + "CC-PDDC": "CC-PDDC", + "CC-PDM-1.0": "CC-PDM-1.0", + "CC-SA-1.0": "CC-SA-1.0", + "CC0-1.0": "CC0-1.0", + "CDDL-1.0": "CDDL-1.0", + "CDDL-1.1": "CDDL-1.1", + "CDL-1.0": "CDL-1.0", + "CDLA-PERMISSIVE-1.0": "CDLA-Permissive-1.0", + "CDLA-PERMISSIVE-2.0": "CDLA-Permissive-2.0", + "CDLA-SHARING-1.0": "CDLA-Sharing-1.0", + "CECILL-1.0": "CECILL-1.0", + "CECILL-1.1": "CECILL-1.1", + "CECILL-2.0": "CECILL-2.0", + "CECILL-2.1": "CECILL-2.1", + "CECILL-B": "CECILL-B", + "CECILL-C": "CECILL-C", + "CERN-OHL-1.1": "CERN-OHL-1.1", + "CERN-OHL-1.2": "CERN-OHL-1.2", + "CERN-OHL-P-2.0": "CERN-OHL-P-2.0", + "CERN-OHL-S-2.0": "CERN-OHL-S-2.0", + "CERN-OHL-W-2.0": "CERN-OHL-W-2.0", + "CFITSIO": "CFITSIO", + "CHECK-CVS": "check-cvs", + "CHECKMK": "checkmk", + "CLARTISTIC": "ClArtistic", + "CLIPS": "Clips", + "CMU-MACH": "CMU-Mach", + "CMU-MACH-NODOC": "CMU-Mach-nodoc", + "CNRI-JYTHON": "CNRI-Jython", + "CNRI-PYTHON": "CNRI-Python", + "CNRI-PYTHON-GPL-COMPATIBLE": "CNRI-Python-GPL-Compatible", + "COIL-1.0": "COIL-1.0", + "COMMUNITY-SPEC-1.0": "Community-Spec-1.0", + "CONDOR-1.1": "Condor-1.1", + "COPYLEFT-NEXT-0.3.0": "copyleft-next-0.3.0", + "COPYLEFT-NEXT-0.3.1": "copyleft-next-0.3.1", + "CORNELL-LOSSLESS-JPEG": "Cornell-Lossless-JPEG", + "CPAL-1.0": "CPAL-1.0", + "CPL-1.0": "CPL-1.0", + "CPOL-1.02": "CPOL-1.02", + "CRONYX": "Cronyx", + "CROSSWORD": "Crossword", + "CRYPTOSWIFT": "CryptoSwift", + "CRYSTALSTACKER": "CrystalStacker", + "CUA-OPL-1.0": "CUA-OPL-1.0", + "CUBE": "Cube", + "CURL": "curl", + "CVE-TOU": "cve-tou", + "D-FSL-1.0": "D-FSL-1.0", + "DEC-3-CLAUSE": "DEC-3-Clause", + "DIFFMARK": "diffmark", + "DL-DE-BY-2.0": "DL-DE-BY-2.0", + "DL-DE-ZERO-2.0": "DL-DE-ZERO-2.0", + "DOC": "DOC", + "DOCBOOK-DTD": "DocBook-DTD", + "DOCBOOK-SCHEMA": "DocBook-Schema", + "DOCBOOK-STYLESHEET": "DocBook-Stylesheet", + "DOCBOOK-XML": "DocBook-XML", + "DOTSEQN": "Dotseqn", + "DRL-1.0": "DRL-1.0", + "DRL-1.1": "DRL-1.1", + "DSDP": "DSDP", + "DTOA": "dtoa", + "DVIPDFM": "dvipdfm", + "ECL-1.0": "ECL-1.0", + "ECL-2.0": "ECL-2.0", + "EFL-1.0": "EFL-1.0", + "EFL-2.0": "EFL-2.0", + "EGENIX": "eGenix", + "ELASTIC-2.0": "Elastic-2.0", + "ENTESSA": "Entessa", + "EPICS": "EPICS", + "EPL-1.0": "EPL-1.0", + "EPL-2.0": "EPL-2.0", + "ERLPL-1.1": "ErlPL-1.1", + "ESA-PL-PERMISSIVE-2.4": "ESA-PL-permissive-2.4", + "ESA-PL-STRONG-COPYLEFT-2.4": "ESA-PL-strong-copyleft-2.4", + "ESA-PL-WEAK-COPYLEFT-2.4": "ESA-PL-weak-copyleft-2.4", + "ETALAB-2.0": "etalab-2.0", + "EUDATAGRID": "EUDatagrid", + "EUPL-1.0": "EUPL-1.0", + "EUPL-1.1": "EUPL-1.1", + "EUPL-1.2": "EUPL-1.2", + "EUROSYM": "Eurosym", + "FAIR": "Fair", + "FBM": "FBM", + "FDK-AAC": "FDK-AAC", + "FERGUSON-TWOFISH": "Ferguson-Twofish", + "FRAMEWORX-1.0": "Frameworx-1.0", + "FREEBSD-DOC": "FreeBSD-DOC", + "FREEIMAGE": "FreeImage", + "FSFAP": "FSFAP", + "FSFAP-NO-WARRANTY-DISCLAIMER": "FSFAP-no-warranty-disclaimer", + "FSFUL": "FSFUL", + "FSFULLR": "FSFULLR", + "FSFULLRSD": "FSFULLRSD", + "FSFULLRWD": "FSFULLRWD", + "FSL-1.1-ALV2": "FSL-1.1-ALv2", + "FSL-1.1-MIT": "FSL-1.1-MIT", + "FTL": "FTL", + "FURUSETH": "Furuseth", + "FWLW": "fwlw", + "GAME-PROGRAMMING-GEMS": "Game-Programming-Gems", + "GCR-DOCS": "GCR-docs", + "GD": "GD", + "GENERIC-XTS": "generic-xts", + "GFDL-1.1-INVARIANTS-ONLY": "GFDL-1.1-invariants-only", + "GFDL-1.1-INVARIANTS-OR-LATER": "GFDL-1.1-invariants-or-later", + "GFDL-1.1-NO-INVARIANTS-ONLY": "GFDL-1.1-no-invariants-only", + "GFDL-1.1-NO-INVARIANTS-OR-LATER": "GFDL-1.1-no-invariants-or-later", + "GFDL-1.1-ONLY": "GFDL-1.1-only", + "GFDL-1.1-OR-LATER": "GFDL-1.1-or-later", + "GFDL-1.2-INVARIANTS-ONLY": "GFDL-1.2-invariants-only", + "GFDL-1.2-INVARIANTS-OR-LATER": "GFDL-1.2-invariants-or-later", + "GFDL-1.2-NO-INVARIANTS-ONLY": "GFDL-1.2-no-invariants-only", + "GFDL-1.2-NO-INVARIANTS-OR-LATER": "GFDL-1.2-no-invariants-or-later", + "GFDL-1.2-ONLY": "GFDL-1.2-only", + "GFDL-1.2-OR-LATER": "GFDL-1.2-or-later", + "GFDL-1.3-INVARIANTS-ONLY": "GFDL-1.3-invariants-only", + "GFDL-1.3-INVARIANTS-OR-LATER": "GFDL-1.3-invariants-or-later", + "GFDL-1.3-NO-INVARIANTS-ONLY": "GFDL-1.3-no-invariants-only", + "GFDL-1.3-NO-INVARIANTS-OR-LATER": "GFDL-1.3-no-invariants-or-later", + "GFDL-1.3-ONLY": "GFDL-1.3-only", + "GFDL-1.3-OR-LATER": "GFDL-1.3-or-later", + "GIFTWARE": "Giftware", + "GL2PS": "GL2PS", + "GLIDE": "Glide", + "GLULXE": "Glulxe", + "GLWTPL": "GLWTPL", + "GNUPLOT": "gnuplot", + "GPL-1.0-ONLY": "GPL-1.0-only", + "GPL-1.0-OR-LATER": "GPL-1.0-or-later", + "GPL-2.0-ONLY": "GPL-2.0-only", + "GPL-2.0-OR-LATER": "GPL-2.0-or-later", + "GPL-3.0-ONLY": "GPL-3.0-only", + "GPL-3.0-OR-LATER": "GPL-3.0-or-later", + "GRAPHICS-GEMS": "Graphics-Gems", + "GSOAP-1.3B": "gSOAP-1.3b", + "GTKBOOK": "gtkbook", + "GUTMANN": "Gutmann", + "HASKELLREPORT": "HaskellReport", + "HDF5": "HDF5", + "HDPARM": "hdparm", + "HIDAPI": "HIDAPI", + "HIPPOCRATIC-2.1": "Hippocratic-2.1", + "HP-1986": "HP-1986", + "HP-1989": "HP-1989", + "HPND": "HPND", + "HPND-DEC": "HPND-DEC", + "HPND-DOC": "HPND-doc", + "HPND-DOC-SELL": "HPND-doc-sell", + "HPND-EXPORT-US": "HPND-export-US", + "HPND-EXPORT-US-ACKNOWLEDGEMENT": "HPND-export-US-acknowledgement", + "HPND-EXPORT-US-MODIFY": "HPND-export-US-modify", + "HPND-EXPORT2-US": "HPND-export2-US", + "HPND-FENNEBERG-LIVINGSTON": "HPND-Fenneberg-Livingston", + "HPND-INRIA-IMAG": "HPND-INRIA-IMAG", + "HPND-INTEL": "HPND-Intel", + "HPND-KEVLIN-HENNEY": "HPND-Kevlin-Henney", + "HPND-MARKUS-KUHN": "HPND-Markus-Kuhn", + "HPND-MERCHANTABILITY-VARIANT": "HPND-merchantability-variant", + "HPND-MIT-DISCLAIMER": "HPND-MIT-disclaimer", + "HPND-NETREK": "HPND-Netrek", + "HPND-PBMPLUS": "HPND-Pbmplus", + "HPND-SELL-MIT-DISCLAIMER-XSERVER": "HPND-sell-MIT-disclaimer-xserver", + "HPND-SELL-REGEXPR": "HPND-sell-regexpr", + "HPND-SELL-VARIANT": "HPND-sell-variant", + "HPND-SELL-VARIANT-CRITICAL-SYSTEMS": "HPND-sell-variant-critical-systems", + "HPND-SELL-VARIANT-MIT-DISCLAIMER": "HPND-sell-variant-MIT-disclaimer", + "HPND-SELL-VARIANT-MIT-DISCLAIMER-REV": "HPND-sell-variant-MIT-disclaimer-rev", + "HPND-SMC": "HPND-SMC", + "HPND-UC": "HPND-UC", + "HPND-UC-EXPORT-US": "HPND-UC-export-US", + "HTMLTIDY": "HTMLTIDY", + "HYPHEN-BULGARIAN": "hyphen-bulgarian", + "IBM-PIBS": "IBM-pibs", + "ICU": "ICU", + "IEC-CODE-COMPONENTS-EULA": "IEC-Code-Components-EULA", + "IJG": "IJG", + "IJG-SHORT": "IJG-short", + "IMAGEMAGICK": "ImageMagick", + "IMATIX": "iMatix", + "IMLIB2": "Imlib2", + "INFO-ZIP": "Info-ZIP", + "INNER-NET-2.0": "Inner-Net-2.0", + "INNOSETUP": "InnoSetup", + "INTEL": "Intel", + "INTEL-ACPI": "Intel-ACPI", + "INTERBASE-1.0": "Interbase-1.0", + "IPA": "IPA", + "IPL-1.0": "IPL-1.0", + "ISC": "ISC", + "ISC-VEILLARD": "ISC-Veillard", + "ISO-PERMISSION": "ISO-permission", + "JAM": "Jam", + "JASPER-2.0": "JasPer-2.0", + "JOVE": "jove", + "JPL-IMAGE": "JPL-image", + "JPNIC": "JPNIC", + "JSON": "JSON", + "KASTRUP": "Kastrup", + "KAZLIB": "Kazlib", + "KNUTH-CTAN": "Knuth-CTAN", + "LAL-1.2": "LAL-1.2", + "LAL-1.3": "LAL-1.3", + "LATEX2E": "Latex2e", + "LATEX2E-TRANSLATED-NOTICE": "Latex2e-translated-notice", + "LEPTONICA": "Leptonica", + "LGPL-2.0-ONLY": "LGPL-2.0-only", + "LGPL-2.0-OR-LATER": "LGPL-2.0-or-later", + "LGPL-2.1-ONLY": "LGPL-2.1-only", + "LGPL-2.1-OR-LATER": "LGPL-2.1-or-later", + "LGPL-3.0-ONLY": "LGPL-3.0-only", + "LGPL-3.0-OR-LATER": "LGPL-3.0-or-later", + "LGPLLR": "LGPLLR", + "LIBPNG": "Libpng", + "LIBPNG-1.6.35": "libpng-1.6.35", + "LIBPNG-2.0": "libpng-2.0", + "LIBSELINUX-1.0": "libselinux-1.0", + "LIBTIFF": "libtiff", + "LIBUTIL-DAVID-NUGENT": "libutil-David-Nugent", + "LILIQ-P-1.1": "LiLiQ-P-1.1", + "LILIQ-R-1.1": "LiLiQ-R-1.1", + "LILIQ-RPLUS-1.1": "LiLiQ-Rplus-1.1", + "LINUX-MAN-PAGES-1-PARA": "Linux-man-pages-1-para", + "LINUX-MAN-PAGES-COPYLEFT": "Linux-man-pages-copyleft", + "LINUX-MAN-PAGES-COPYLEFT-2-PARA": "Linux-man-pages-copyleft-2-para", + "LINUX-MAN-PAGES-COPYLEFT-VAR": "Linux-man-pages-copyleft-var", + "LINUX-OPENIB": "Linux-OpenIB", + "LOOP": "LOOP", + "LPD-DOCUMENT": "LPD-document", + "LPL-1.0": "LPL-1.0", + "LPL-1.02": "LPL-1.02", + "LPPL-1.0": "LPPL-1.0", + "LPPL-1.1": "LPPL-1.1", + "LPPL-1.2": "LPPL-1.2", + "LPPL-1.3A": "LPPL-1.3a", + "LPPL-1.3C": "LPPL-1.3c", + "LSOF": "lsof", + "LUCIDA-BITMAP-FONTS": "Lucida-Bitmap-Fonts", + "LZMA-SDK-9.11-TO-9.20": "LZMA-SDK-9.11-to-9.20", + "LZMA-SDK-9.22": "LZMA-SDK-9.22", + "MACKERRAS-3-CLAUSE": "Mackerras-3-Clause", + "MACKERRAS-3-CLAUSE-ACKNOWLEDGMENT": "Mackerras-3-Clause-acknowledgment", + "MAGAZ": "magaz", + "MAILPRIO": "mailprio", + "MAKEINDEX": "MakeIndex", + "MAN2HTML": "man2html", + "MARTIN-BIRGMEIER": "Martin-Birgmeier", + "MCPHEE-SLIDESHOW": "McPhee-slideshow", + "METAMAIL": "metamail", + "MINPACK": "Minpack", + "MIPS": "MIPS", + "MIROS": "MirOS", + "MIT": "MIT", + "MIT-0": "MIT-0", + "MIT-ADVERTISING": "MIT-advertising", + "MIT-CLICK": "MIT-Click", + "MIT-CMU": "MIT-CMU", + "MIT-ENNA": "MIT-enna", + "MIT-FEH": "MIT-feh", + "MIT-FESTIVAL": "MIT-Festival", + "MIT-KHRONOS-OLD": "MIT-Khronos-old", + "MIT-MODERN-VARIANT": "MIT-Modern-Variant", + "MIT-OPEN-GROUP": "MIT-open-group", + "MIT-STK": "MIT-STK", + "MIT-TESTREGEX": "MIT-testregex", + "MIT-WU": "MIT-Wu", + "MITNFA": "MITNFA", + "MMIXWARE": "MMIXware", + "MMPL-1.0.1": "MMPL-1.0.1", + "MOTOSOTO": "Motosoto", + "MPEG-SSG": "MPEG-SSG", + "MPI-PERMISSIVE": "mpi-permissive", + "MPICH2": "mpich2", + "MPL-1.0": "MPL-1.0", + "MPL-1.1": "MPL-1.1", + "MPL-2.0": "MPL-2.0", + "MPL-2.0-NO-COPYLEFT-EXCEPTION": "MPL-2.0-no-copyleft-exception", + "MPLUS": "mplus", + "MS-LPL": "MS-LPL", + "MS-PL": "MS-PL", + "MS-RL": "MS-RL", + "MTLL": "MTLL", + "MULANPSL-1.0": "MulanPSL-1.0", + "MULANPSL-2.0": "MulanPSL-2.0", + "MULTICS": "Multics", + "MUP": "Mup", + "NAIST-2003": "NAIST-2003", + "NASA-1.3": "NASA-1.3", + "NAUMEN": "Naumen", + "NBPL-1.0": "NBPL-1.0", + "NCBI-PD": "NCBI-PD", + "NCGL-UK-2.0": "NCGL-UK-2.0", + "NCL": "NCL", + "NCSA": "NCSA", + "NETCDF": "NetCDF", + "NEWSLETR": "Newsletr", + "NGPL": "NGPL", + "NGREP": "ngrep", + "NICTA-1.0": "NICTA-1.0", + "NIST-PD": "NIST-PD", + "NIST-PD-FALLBACK": "NIST-PD-fallback", + "NIST-PD-TNT": "NIST-PD-TNT", + "NIST-SOFTWARE": "NIST-Software", + "NLOD-1.0": "NLOD-1.0", + "NLOD-2.0": "NLOD-2.0", + "NLPL": "NLPL", + "NOKIA": "Nokia", + "NOSL": "NOSL", + "NOWEB": "Noweb", + "NPL-1.0": "NPL-1.0", + "NPL-1.1": "NPL-1.1", + "NPOSL-3.0": "NPOSL-3.0", + "NRL": "NRL", + "NTIA-PD": "NTIA-PD", + "NTP": "NTP", + "NTP-0": "NTP-0", + "O-UDA-1.0": "O-UDA-1.0", + "OAR": "OAR", + "OCCT-PL": "OCCT-PL", + "OCLC-2.0": "OCLC-2.0", + "ODBL-1.0": "ODbL-1.0", + "ODC-BY-1.0": "ODC-By-1.0", + "OFFIS": "OFFIS", + "OFL-1.0": "OFL-1.0", + "OFL-1.0-NO-RFN": "OFL-1.0-no-RFN", + "OFL-1.0-RFN": "OFL-1.0-RFN", + "OFL-1.1": "OFL-1.1", + "OFL-1.1-NO-RFN": "OFL-1.1-no-RFN", + "OFL-1.1-RFN": "OFL-1.1-RFN", + "OGC-1.0": "OGC-1.0", + "OGDL-TAIWAN-1.0": "OGDL-Taiwan-1.0", + "OGL-CANADA-2.0": "OGL-Canada-2.0", + "OGL-UK-1.0": "OGL-UK-1.0", + "OGL-UK-2.0": "OGL-UK-2.0", + "OGL-UK-3.0": "OGL-UK-3.0", + "OGTSL": "OGTSL", + "OLDAP-1.1": "OLDAP-1.1", + "OLDAP-1.2": "OLDAP-1.2", + "OLDAP-1.3": "OLDAP-1.3", + "OLDAP-1.4": "OLDAP-1.4", + "OLDAP-2.0": "OLDAP-2.0", + "OLDAP-2.0.1": "OLDAP-2.0.1", + "OLDAP-2.1": "OLDAP-2.1", + "OLDAP-2.2": "OLDAP-2.2", + "OLDAP-2.2.1": "OLDAP-2.2.1", + "OLDAP-2.2.2": "OLDAP-2.2.2", + "OLDAP-2.3": "OLDAP-2.3", + "OLDAP-2.4": "OLDAP-2.4", + "OLDAP-2.5": "OLDAP-2.5", + "OLDAP-2.6": "OLDAP-2.6", + "OLDAP-2.7": "OLDAP-2.7", + "OLDAP-2.8": "OLDAP-2.8", + "OLFL-1.3": "OLFL-1.3", + "OML": "OML", + "OPENMDW-1.0": "OpenMDW-1.0", + "OPENPBS-2.3": "OpenPBS-2.3", + "OPENSSL": "OpenSSL", + "OPENSSL-STANDALONE": "OpenSSL-standalone", + "OPENVISION": "OpenVision", + "OPL-1.0": "OPL-1.0", + "OPL-UK-3.0": "OPL-UK-3.0", + "OPUBL-1.0": "OPUBL-1.0", + "OSC-1.0": "OSC-1.0", + "OSET-PL-2.1": "OSET-PL-2.1", + "OSL-1.0": "OSL-1.0", + "OSL-1.1": "OSL-1.1", + "OSL-2.0": "OSL-2.0", + "OSL-2.1": "OSL-2.1", + "OSL-3.0": "OSL-3.0", + "OSSP": "OSSP", + "PADL": "PADL", + "PARATYPE-FREE-FONT-1.3": "ParaType-Free-Font-1.3", + "PARITY-6.0.0": "Parity-6.0.0", + "PARITY-7.0.0": "Parity-7.0.0", + "PDDL-1.0": "PDDL-1.0", + "PHP-3.0": "PHP-3.0", + "PHP-3.01": "PHP-3.01", + "PIXAR": "Pixar", + "PKGCONF": "pkgconf", + "PLEXUS": "Plexus", + "PNMSTITCH": "pnmstitch", + "POLYFORM-NONCOMMERCIAL-1.0.0": "PolyForm-Noncommercial-1.0.0", + "POLYFORM-SMALL-BUSINESS-1.0.0": "PolyForm-Small-Business-1.0.0", + "POSTGRESQL": "PostgreSQL", + "PPL": "PPL", + "PSF-2.0": "PSF-2.0", + "PSFRAG": "psfrag", + "PSUTILS": "psutils", + "PYTHON-2.0": "Python-2.0", + "PYTHON-2.0.1": "Python-2.0.1", + "PYTHON-LDAP": "python-ldap", + "QHULL": "Qhull", + "QPL-1.0": "QPL-1.0", + "QPL-1.0-INRIA-2004": "QPL-1.0-INRIA-2004", + "RADVD": "radvd", + "RDISC": "Rdisc", + "RHECOS-1.1": "RHeCos-1.1", + "RPL-1.1": "RPL-1.1", + "RPL-1.5": "RPL-1.5", + "RPSL-1.0": "RPSL-1.0", + "RSA-MD": "RSA-MD", + "RSCPL": "RSCPL", + "RUBY": "Ruby", + "RUBY-PTY": "Ruby-pty", + "SAX-PD": "SAX-PD", + "SAX-PD-2.0": "SAX-PD-2.0", + "SAXPATH": "Saxpath", + "SCEA": "SCEA", + "SCHEMEREPORT": "SchemeReport", + "SENDMAIL": "Sendmail", + "SENDMAIL-8.23": "Sendmail-8.23", + "SENDMAIL-OPEN-SOURCE-1.1": "Sendmail-Open-Source-1.1", + "SGI-B-1.0": "SGI-B-1.0", + "SGI-B-1.1": "SGI-B-1.1", + "SGI-B-2.0": "SGI-B-2.0", + "SGI-OPENGL": "SGI-OpenGL", + "SGMLUG-PM": "SGMLUG-PM", + "SGP4": "SGP4", + "SHL-0.5": "SHL-0.5", + "SHL-0.51": "SHL-0.51", + "SIMPL-2.0": "SimPL-2.0", + "SISSL": "SISSL", + "SISSL-1.2": "SISSL-1.2", + "SL": "SL", + "SLEEPYCAT": "Sleepycat", + "SMAIL-GPL": "SMAIL-GPL", + "SMLNJ": "SMLNJ", + "SMPPL": "SMPPL", + "SNIA": "SNIA", + "SNPRINTF": "snprintf", + "SOFA": "SOFA", + "SOFTSURFER": "softSurfer", + "SOUNDEX": "Soundex", + "SPENCER-86": "Spencer-86", + "SPENCER-94": "Spencer-94", + "SPENCER-99": "Spencer-99", + "SPL-1.0": "SPL-1.0", + "SSH-KEYSCAN": "ssh-keyscan", + "SSH-OPENSSH": "SSH-OpenSSH", + "SSH-SHORT": "SSH-short", + "SSLEAY-STANDALONE": "SSLeay-standalone", + "SSPL-1.0": "SSPL-1.0", + "SUGARCRM-1.1.3": "SugarCRM-1.1.3", + "SUL-1.0": "SUL-1.0", + "SUN-PPP": "Sun-PPP", + "SUN-PPP-2000": "Sun-PPP-2000", + "SUNPRO": "SunPro", + "SWL": "SWL", + "SWRULE": "swrule", + "SYMLINKS": "Symlinks", + "TAPR-OHL-1.0": "TAPR-OHL-1.0", + "TCL": "TCL", + "TCP-WRAPPERS": "TCP-wrappers", + "TEKHVC": "TekHVC", + "TERMREADKEY": "TermReadKey", + "TGPPL-1.0": "TGPPL-1.0", + "THIRDEYE": "ThirdEye", + "THREEPARTTABLE": "threeparttable", + "TMATE": "TMate", + "TORQUE-1.1": "TORQUE-1.1", + "TOSL": "TOSL", + "TPDL": "TPDL", + "TPL-1.0": "TPL-1.0", + "TRUSTEDQSL": "TrustedQSL", + "TTWL": "TTWL", + "TTYP0": "TTYP0", + "TU-BERLIN-1.0": "TU-Berlin-1.0", + "TU-BERLIN-2.0": "TU-Berlin-2.0", + "UBUNTU-FONT-1.0": "Ubuntu-font-1.0", + "UCAR": "UCAR", + "UCL-1.0": "UCL-1.0", + "ULEM": "ulem", + "UMICH-MERIT": "UMich-Merit", + "UNICODE-3.0": "Unicode-3.0", + "UNICODE-DFS-2015": "Unicode-DFS-2015", + "UNICODE-DFS-2016": "Unicode-DFS-2016", + "UNICODE-TOU": "Unicode-TOU", + "UNIXCRYPT": "UnixCrypt", + "UNLICENSE": "Unlicense", + "UNLICENSE-LIBTELNET": "Unlicense-libtelnet", + "UNLICENSE-LIBWHIRLPOOL": "Unlicense-libwhirlpool", + "UNRAR": "UnRAR", + "UPL-1.0": "UPL-1.0", + "URT-RLE": "URT-RLE", + "VIM": "Vim", + "VIXIE-CRON": "Vixie-Cron", + "VOSTROM": "VOSTROM", + "VSL-1.0": "VSL-1.0", + "W3C": "W3C", + "W3C-19980720": "W3C-19980720", + "W3C-20150513": "W3C-20150513", + "W3M": "w3m", + "WATCOM-1.0": "Watcom-1.0", + "WIDGET-WORKSHOP": "Widget-Workshop", + "WORDNET": "WordNet", + "WSUIPA": "Wsuipa", + "WTFNMFPL": "WTFNMFPL", + "WTFPL": "WTFPL", + "WWL": "wwl", + "X11": "X11", + "X11-DISTRIBUTE-MODIFICATIONS-VARIANT": "X11-distribute-modifications-variant", + "X11-NO-PERMIT-PERSONS": "X11-no-permit-persons", + "X11-SWAPPED": "X11-swapped", + "XDEBUG-1.03": "Xdebug-1.03", + "XEROX": "Xerox", + "XFIG": "Xfig", + "XFREE86-1.1": "XFree86-1.1", + "XINETD": "xinetd", + "XKEYBOARD-CONFIG-ZINOVIEV": "xkeyboard-config-Zinoviev", + "XLOCK": "xlock", + "XNET": "Xnet", + "XPP": "xpp", + "XSKAT": "XSkat", + "XZOOM": "xzoom", + "YPL-1.0": "YPL-1.0", + "YPL-1.1": "YPL-1.1", + "ZED": "Zed", + "ZEEFF": "Zeeff", + "ZEND-2.0": "Zend-2.0", + "ZIMBRA-1.3": "Zimbra-1.3", + "ZIMBRA-1.4": "Zimbra-1.4", + "ZLIB": "Zlib", + "ZLIB-ACKNOWLEDGEMENT": "zlib-acknowledgement", + "ZPL-1.1": "ZPL-1.1", + "ZPL-2.0": "ZPL-2.0", + "ZPL-2.1": "ZPL-2.1", +}