Skip to content

Commit

Permalink
fix: escape and unescape name
Browse files Browse the repository at this point in the history
This commit adds a small fix that starts path-(un)escaping the `name`
component of the purl.

This is required in case of the `name` containing a `/`, which would so
far have created a non-roundtrippable purl...
  • Loading branch information
tommyknows authored and shibumi committed Jul 17, 2023
1 parent 564b6fc commit 3587d8c
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 15 deletions.
37 changes: 22 additions & 15 deletions packageurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,14 @@ func (p *PackageURL) ToString() string {
Fragment: p.Subpath,
}

// we use JoinPath and EscapePath as the behavior for "/" is only correct with that.
nameWithVersion := url.PathEscape(p.Name)
if p.Version != "" {
nameWithVersion += "@" + p.Version
}

// we use JoinPath and EscapedPath as the behavior for "/" is only correct with that.
// We don't want to escape "/", but want to escape all other characters that are necessary.
u = u.JoinPath(p.Type, p.Namespace, strings.Join([]string{p.Name, p.Version}, "@"))
u = u.JoinPath(p.Type, p.Namespace, nameWithVersion)
// write the actual path into the "Opaque" block, so that the generated string at the end is
// pkg:<path> and not pkg://<path>.
u.Opaque, u.Path = u.EscapedPath(), ""
Expand Down Expand Up @@ -259,18 +264,27 @@ func FromString(purl string) (PackageURL, error) {
}

func separateNamespaceNameVersion(path string) (ns, name, version string, err error) {
namespaceSep := strings.LastIndex(path, "/")
if namespaceSep != -1 {
ns, err = url.PathUnescape(path[:namespaceSep])
name = path

if namespaceSep := strings.LastIndex(name, "/"); namespaceSep != -1 {
ns, name = name[:namespaceSep], name[namespaceSep+1:]

ns, err = url.PathUnescape(ns)
if err != nil {
return "", "", "", fmt.Errorf("error unescaping namespace: %w", err)
}
}

if versionSep := strings.LastIndex(name, "@"); versionSep != -1 {
name, version = name[:versionSep], name[versionSep+1:]

path = path[namespaceSep+1:]
version, err = url.PathUnescape(version)
if err != nil {
return "", "", "", fmt.Errorf("error unescaping version: %w", err)
}
}

v := strings.Split(path, "@")
name, err = url.PathUnescape(v[0])
name, err = url.PathUnescape(name)
if err != nil {
return "", "", "", fmt.Errorf("error unescaping name: %w", err)
}
Expand All @@ -279,13 +293,6 @@ func separateNamespaceNameVersion(path string) (ns, name, version string, err er
return "", "", "", fmt.Errorf("purl is missing name")
}

if len(v) > 1 {
version, err = url.PathUnescape(v[1])
if err != nil {
return "", "", "", fmt.Errorf("error unescaping version: %w", err)
}
}

return ns, name, version, nil
}

Expand Down
14 changes: 14 additions & 0 deletions packageurl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,21 @@ func TestQualifiersMapConversion(t *testing.T) {
t.Logf("qualifiers -> map conversion failed: got: %#v, wanted: %#v", mp, test.kvMap)
t.Fail()
}
}
}

func TestNameEscaping(t *testing.T) {
testCases := map[string]string{
"abc": "pkg:abc",
"ab/c": "pkg:ab%2Fc",
}
for name, output := range testCases {
t.Run(name, func(t *testing.T) {
p := &packageurl.PackageURL{Name: name}
if s := p.ToString(); s != output {
t.Fatalf("wrong escape. expected=%q, got=%q", output, s)
}
})
}

}

0 comments on commit 3587d8c

Please sign in to comment.