Skip to content
This repository has been archived by the owner on Oct 14, 2024. It is now read-only.

Commit

Permalink
Fix panic while parsing PURLs
Browse files Browse the repository at this point in the history
Fixes a panic which occurs if an input which is a valid URL doesn't have
either 2 or 3 parts to the opaque once split.

The only valid lengths for PURL opaques once split are 2 and 3, so this
PR returns an empty PURL if the PURL being parsed is invalid.
  • Loading branch information
Tehsmash authored and Sam Betts committed Mar 27, 2023
1 parent f234f43 commit 7f4fa84
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 8 deletions.
42 changes: 34 additions & 8 deletions shared/pkg/analyzer/purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ var validPurlTypes = map[string]struct{}{
"swift": {},
}

func isValidPurlType(t string) bool {
_, ok := validPurlTypes[t]
return ok
}

// nolint:gomnd
func purlStringToStruct(purlInput string) purl {
if purlInput == "" {
return newPurl()
Expand All @@ -105,25 +111,45 @@ func purlStringToStruct(purlInput string) purl {

purlParts := strings.Split(purlURL.Opaque, "/")

// nolint:gomnd
if len(purlParts) == 2 {
// Check type sometimes the anaylzers goof up and forget the
// type part, looking at you syft....
if _, ok := validPurlTypes[purlParts[0]]; ok {
// Purls Opaques have 2 valid formats:
//
// * type/namespace/name@version
// * type/name@version
//
// Namespace part is optional and type specific, the other fields are
// required.
if len(purlParts) == 3 {
output.typ = purlParts[0]
output.namespace = purlParts[1]
} else if len(purlParts) == 2 {
// Check type is a valid PURL type, if it is then use it.
//
// Otherwise check if the type is a namespace we know belong to
// one of the types, sometimes the anaylzers goof up and forget
// the type part, looking at you syft....
//
// If its a recognised namespace then we can correct the PURL,
// otherwise this is an invalid PURL so return the empty purl
// struct.
if isValidPurlType(purlParts[0]) {
output.typ = purlParts[0]
} else {
// Fix known cases otherwise just exclude the type from
// the purl or error maybe, havn't decided
if purlParts[0] == "alpine" {
switch purlParts[0] {
case "alpine":
output.typ = "apk"
output.namespace = "alpine"
default:
return newPurl()
}
}
} else {
output.typ = purlParts[0]
output.namespace = purlParts[1]
// No other length is valid
return newPurl()
}

// Version is optional, so no need to check if we found the separator
name, version, _ := strings.Cut(purlParts[len(purlParts)-1], "@")
output.name = name
output.version = version
Expand Down
120 changes: 120 additions & 0 deletions shared/pkg/analyzer/purl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright © 2022 Cisco Systems, Inc. and its affiliates.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package analyzer

import (
"net/url"
"testing"

"github.com/google/go-cmp/cmp"
)

func Test_purlStringToStruct(t *testing.T) {
type args struct {
purlInput string
}
tests := []struct {
name string
args args
want purl
}{
{
name: "valid purl",
args: args{
purlInput: "pkg:maven/org.apache.xmlgraphics/[email protected]?packaging=sources",
},
want: purl{
scheme: "pkg",
typ: "maven",
namespace: "org.apache.xmlgraphics",
name: "batik-anim",
version: "1.9.1",
qualifers: url.Values{
"packaging": []string{"sources"},
},
subpath: "",
},
},
{
name: "empty purl",
args: args{
purlInput: "",
},
want: newPurl(),
},
{
name: "junk",
args: args{
purlInput: "asdkjnasdkhjbasdhb",
},
want: newPurl(),
},
{
name: "no namespace purl",
args: args{
purlInput: "pkg:pypi/[email protected]",
},
want: purl{
scheme: "pkg",
typ: "pypi",
namespace: "",
name: "Babel",
version: "2.9.1",
qualifers: url.Values{},
subpath: "",
},
},
{
name: "no type or namespace",
args: args{
purlInput: "pkg:[email protected]",
},
want: newPurl(),
},
{
name: "alpine package missing type",
args: args{
purlInput: "pkg:alpine/[email protected]?arch=x86",
},
want: purl{
scheme: "pkg",
typ: "apk",
namespace: "alpine",
name: "apk",
version: "2.12.9-r3",
qualifers: url.Values{
"arch": []string{"x86"},
},
subpath: "",
},
},
{
name: "no namespace, unknown type",
args: args{
purlInput: "pkg:debi/[email protected]?arch=i386&distro=jessie",
},
want: newPurl(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := purlStringToStruct(tt.args.purlInput)
if diff := cmp.Diff(tt.want, got, cmp.AllowUnexported(purl{})); diff != "" {
t.Errorf("purlStringToStruct() mismatch (-want +got):\n%s", diff)
}
})
}
}

0 comments on commit 7f4fa84

Please sign in to comment.