Skip to content

Commit 8df027a

Browse files
committed
fix(java): respect quoted values in the Bundle-License header
1 parent bcb24b3 commit 8df027a

2 files changed

Lines changed: 48 additions & 7 deletions

File tree

pkg/dependency/parser/java/jar/parse.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -696,13 +696,14 @@ func parseManifest(f *zip.File) (manifest, error) {
696696
return m, nil
697697
}
698698

699-
// parseBundleLicense resolves the OSGi Bundle-License header (a comma-separated
700-
// list of "name;attr=value" entries) to SPDX license IDs. Each entry's name is
701-
// tried as an SPDX ID then as a URL, and its ;link= value as a URL; entries that
702-
// resolve to neither (free text, "<<EXTERNAL>>") are skipped.
699+
// parseBundleLicense resolves the OSGi Bundle-License header to SPDX license IDs.
700+
// The header is a comma-separated list of "license-identifier;attr=value" entries.
701+
// The identifier is an SPDX ID or the canonical URL of the license, and the optional link attribute points to the license text.
702+
// Entries that resolve to neither (free text, "<<EXTERNAL>>") are skipped.
703+
// https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#framework.module-bundle-license
703704
func parseBundleLicense(header string) []string {
704705
var names []string
705-
for entry := range strings.SplitSeq(header, ",") {
706+
for _, entry := range splitUnquoted(header, ',') {
706707
name, link := parseBundleLicenseEntry(entry)
707708
if id, ok := resolveBundleLicense(name, link); ok {
708709
names = append(names, id)
@@ -732,16 +733,36 @@ func resolveBundleLicense(name, link string) (string, bool) {
732733
// and the value of its optional ;link attribute. Surrounding spaces and double
733734
// quotes are trimmed from both.
734735
func parseBundleLicenseEntry(entry string) (name, link string) {
735-
fields := strings.Split(entry, ";")
736+
fields := splitUnquoted(entry, ';')
736737
name = strings.Trim(fields[0], ` "`)
737738
for _, attr := range fields[1:] {
738-
if v, ok := strings.CutPrefix(strings.TrimSpace(attr), "link="); ok {
739+
if k, v, ok := strings.Cut(attr, "="); ok && strings.TrimSpace(k) == "link" {
739740
link = strings.Trim(v, ` "`)
740741
}
741742
}
742743
return name, link
743744
}
744745

746+
// splitUnquoted splits s on sep, ignoring separators inside a double-quoted value.
747+
// Attribute values in a manifest header are quoted strings, so a description such as "Apache License, Version 2.0" is a single value rather than two entries.
748+
func splitUnquoted(s string, sep byte) []string {
749+
var fields []string
750+
var quoted bool
751+
start := 0
752+
for i := range len(s) {
753+
switch s[i] {
754+
case '"':
755+
quoted = !quoted
756+
case sep:
757+
if !quoted {
758+
fields = append(fields, s[start:i])
759+
start = i + 1
760+
}
761+
}
762+
}
763+
return append(fields, s[start:])
764+
}
765+
745766
// parsePluginLicenseName extracts the license name from a single Jenkins
746767
// Plugin-License-Name[-N] manifest line, or an empty string when the line is not
747768
// such an attribute or carries no value.

pkg/dependency/parser/java/jar/parse_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,26 @@ func TestParseBundleLicense(t *testing.T) {
648648
header: "Apache-2.0, https://opensource.org/licenses/MIT",
649649
want: []string{"Apache-2.0", "MIT"},
650650
},
651+
{
652+
name: "comma inside a quoted description does not split the entry",
653+
header: `Apache-2.0;description="Apache License, Version 2.0";link="https://www.apache.org/licenses/LICENSE-2.0"`,
654+
want: []string{"Apache-2.0"},
655+
},
656+
{
657+
name: "comma inside a quoted name does not split the entry",
658+
header: `"Eclipse Public License, Version 1.0";link="http://www.eclipse.org/legal/epl-v10.html"`,
659+
want: []string{"EPL-1.0"},
660+
},
661+
{
662+
name: "semicolon inside a quoted name does not start an attribute",
663+
header: `"Custom; License";link="https://opensource.org/licenses/MIT"`,
664+
want: []string{"MIT"},
665+
},
666+
{
667+
name: "spaces around the attribute assignment",
668+
header: `"Custom" ; link = "https://opensource.org/licenses/MIT"`,
669+
want: []string{"MIT"},
670+
},
651671
{
652672
name: "EXTERNAL token is skipped",
653673
header: "<<EXTERNAL>>",

0 commit comments

Comments
 (0)