Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions syft/pkg/cataloger/binary/pe_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ var (
// spaceRegex includes nbsp (#160) considered to be a space character
spaceRegex = regexp.MustCompile(`[\s\xa0]+`)
numberRegex = regexp.MustCompile(`\d`)
// commaSeparatedVersionRegex matches the comma-separated form that Windows
// VERSIONINFO resources often carry (mirroring the FILEVERSION x,y,z,w
// declaration in a .rc file), e.g. "3, 0, 21, 0"
commaSeparatedVersionRegex = regexp.MustCompile(`^\d+(?:\s*,\s*\d+)+$`)
)

func newPEPackage(versionResources map[string]string, f file.Location) pkg.Package {
Expand Down Expand Up @@ -129,6 +133,7 @@ func findVersionFromVR(versionResources map[string]string) string {

func extractVersionFromResourcesValue(version string) string {
version = strings.TrimSpace(version)
version = normalizeCommaSeparatedVersion(version)
out := ""
for i, f := range strings.Fields(version) {
if containsNumber(out) && !containsNumber(f) {
Expand All @@ -143,6 +148,23 @@ func extractVersionFromResourcesValue(version string) string {
return out
}

// normalizeCommaSeparatedVersion converts the comma-separated version form used
// by Windows VERSIONINFO resources into the canonical dotted form, e.g.
// "3, 0, 21, 0" becomes "3.0.21.0". Values that are not entirely made up of
// comma-separated numbers are returned unchanged.
func normalizeCommaSeparatedVersion(version string) string {
if !commaSeparatedVersionRegex.MatchString(version) {
return version
}

fields := strings.Split(version, ",")
for i := range fields {
fields[i] = strings.TrimSpace(fields[i])
}

return strings.Join(fields, ".")
}

func containsNumber(s string) bool {
return numberRegex.MatchString(s)
}
43 changes: 43 additions & 0 deletions syft/pkg/cataloger/binary/pe_package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package binary
import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/anchore/syft/syft/file"
)

Expand All @@ -22,3 +24,44 @@ func TestGhostscriptPEGeneratesGenericPURL(t *testing.T) {
t.Fatalf("expected purl %q, got %q", expected, p.PURL)
}
}

func Test_findVersionFromVR(t *testing.T) {
tests := []struct {
name string
versionResources map[string]string
want string
}{
{
name: "dotted version is kept as-is",
versionResources: map[string]string{"ProductVersion": "3.0.21.0"},
want: "3.0.21.0",
},
{
name: "comma separated version is normalized",
// VERSIONINFO resources often mirror the .rc FILEVERSION 3,0,21,0 form
versionResources: map[string]string{"ProductVersion": "3, 0, 21, 0"},
want: "3.0.21.0",
},
{
name: "comma separated version without spaces is normalized",
versionResources: map[string]string{"FileVersion": "3,0,21,0"},
want: "3.0.21.0",
},
{
name: "version with trailing comment is unaffected",
versionResources: map[string]string{"ProductVersion": "1.2.3 (build 456)"},
want: "1.2.3",
},
{
name: "non numeric comma separated value is kept as-is",
versionResources: map[string]string{"ProductVersion": "1.0, beta"},
want: "1.0,",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, findVersionFromVR(tt.versionResources))
})
}
}
Loading