diff --git a/packageurl.go b/packageurl.go index 4f688a8..221f22f 100644 --- a/packageurl.go +++ b/packageurl.go @@ -382,6 +382,10 @@ func pathEscape(s string) string { switch { case c == '@': t.WriteString("%40") + case c == '+': + // url.PathEscape doesn't encode '+' since it's a valid query escape character for ' ' in application/x-www-form-urlencoded, but '+' is a + // valid character in semver so we don't want it to be unintentionally unescaped as ' ' by downstream parsers of the purl. + t.WriteString("%2B") case c == '?' || c == '#' || c == ' ' || c > unicode.MaxASCII: t.WriteString(url.PathEscape(string(c))) default: diff --git a/packageurl_test.go b/packageurl_test.go index 480fba8..d9142ea 100644 --- a/packageurl_test.go +++ b/packageurl_test.go @@ -354,8 +354,8 @@ func TestEncoding(t *testing.T) { }, { name: "pre-encoded version is unchanged", - input: "pkg:type/name/space/name@versio%20n?key=value#sub/path", - expected: "pkg:type/name/space/name@versio%20n?key=value#sub/path", + input: "pkg:type/name/space/name@versio%20n%2Bbeta?key=value#sub/path", + expected: "pkg:type/name/space/name@versio%20n%2Bbeta?key=value#sub/path", }, { name: "unencoded qualifier value is encoded",