mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
683 windows filepath (#735)
Support Windows Directory Resolver Add function that converts windows to posix functionality Add function that converts posix to windows Add build tags to remove windows developer environment errors redact carriage return specific windows issues Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
2a7325a965
commit
01dc78ccc3
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package testutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/go-presenter"
|
"github.com/anchore/go-presenter"
|
||||||
@ -51,6 +52,7 @@ func AssertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Pres
|
|||||||
var expected = testutils.GetGoldenFileContents(t)
|
var expected = testutils.GetGoldenFileContents(t)
|
||||||
|
|
||||||
// remove dynamic values, which should be tested independently
|
// remove dynamic values, which should be tested independently
|
||||||
|
redactors = append(redactors, carriageRedactor)
|
||||||
for _, r := range redactors {
|
for _, r := range redactors {
|
||||||
actual = r(actual)
|
actual = r(actual)
|
||||||
expected = r(expected)
|
expected = r(expected)
|
||||||
@ -79,6 +81,7 @@ func AssertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter
|
|||||||
var expected = testutils.GetGoldenFileContents(t)
|
var expected = testutils.GetGoldenFileContents(t)
|
||||||
|
|
||||||
// remove dynamic values, which should be tested independently
|
// remove dynamic values, which should be tested independently
|
||||||
|
redactors = append(redactors, carriageRedactor)
|
||||||
for _, r := range redactors {
|
for _, r := range redactors {
|
||||||
actual = r(actual)
|
actual = r(actual)
|
||||||
expected = r(expected)
|
expected = r(expected)
|
||||||
@ -138,6 +141,11 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func carriageRedactor(s []byte) []byte {
|
||||||
|
msg := strings.ReplaceAll(string(s), "\r\n", "\n")
|
||||||
|
return []byte(msg)
|
||||||
|
}
|
||||||
|
|
||||||
func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
@ -21,6 +22,8 @@ import (
|
|||||||
"github.com/wagoodman/go-progress"
|
"github.com/wagoodman/go-progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const WindowsOS = "windows"
|
||||||
|
|
||||||
var unixSystemRuntimePrefixes = []string{
|
var unixSystemRuntimePrefixes = []string{
|
||||||
"/proc",
|
"/proc",
|
||||||
"/dev",
|
"/dev",
|
||||||
@ -143,6 +146,11 @@ func (r *directoryResolver) indexPath(path string, info os.FileInfo, err error)
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// here we check to see if we need to normalize paths to posix on the way in coming from windows
|
||||||
|
if runtime.GOOS == WindowsOS {
|
||||||
|
path = windowsToPosix(path)
|
||||||
|
}
|
||||||
|
|
||||||
newRoot, err := r.addPathToIndex(path, info)
|
newRoot, err := r.addPathToIndex(path, info)
|
||||||
if r.isFileAccessErr(path, err) {
|
if r.isFileAccessErr(path, err) {
|
||||||
return "", nil
|
return "", nil
|
||||||
@ -258,6 +266,11 @@ func (r directoryResolver) requestPath(userPath string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r directoryResolver) responsePath(path string) string {
|
func (r directoryResolver) responsePath(path string) string {
|
||||||
|
// check to see if we need to encode back to Windows from posix
|
||||||
|
if runtime.GOOS == WindowsOS {
|
||||||
|
path = posixToWindows(path)
|
||||||
|
}
|
||||||
|
|
||||||
// always return references relative to the request path (not absolute path)
|
// always return references relative to the request path (not absolute path)
|
||||||
if filepath.IsAbs(path) {
|
if filepath.IsAbs(path) {
|
||||||
// we need to account for the cwd relative to the running process and the given root for the directory resolver
|
// we need to account for the cwd relative to the running process and the given root for the directory resolver
|
||||||
@ -314,6 +327,10 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == WindowsOS {
|
||||||
|
userStrPath = windowsToPosix(userStrPath)
|
||||||
|
}
|
||||||
|
|
||||||
exists, ref, err := r.fileTree.File(file.Path(userStrPath))
|
exists, ref, err := r.fileTree.File(file.Path(userStrPath))
|
||||||
if err == nil && exists {
|
if err == nil && exists {
|
||||||
references = append(references, NewLocationFromDirectory(r.responsePath(userStrPath), *ref))
|
references = append(references, NewLocationFromDirectory(r.responsePath(userStrPath), *ref))
|
||||||
@ -367,7 +384,13 @@ func (r directoryResolver) FileContentsByLocation(location Location) (io.ReadClo
|
|||||||
// by preference or these files are not readable by the current user).
|
// by preference or these files are not readable by the current user).
|
||||||
return nil, fmt.Errorf("file content is inaccessible path=%q", location.ref.RealPath)
|
return nil, fmt.Errorf("file content is inaccessible path=%q", location.ref.RealPath)
|
||||||
}
|
}
|
||||||
return file.NewLazyReadCloser(string(location.ref.RealPath)), nil
|
// RealPath is posix so for windows directory resolver we need to translate
|
||||||
|
// to its true on disk path.
|
||||||
|
filePath := string(location.ref.RealPath)
|
||||||
|
if runtime.GOOS == WindowsOS {
|
||||||
|
filePath = posixToWindows(filePath)
|
||||||
|
}
|
||||||
|
return file.NewLazyReadCloser(filePath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r directoryResolver) isInIndex(location Location) bool {
|
func (r directoryResolver) isInIndex(location Location) bool {
|
||||||
@ -409,6 +432,33 @@ func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error)
|
|||||||
return locations, nil
|
return locations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func windowsToPosix(windowsPath string) (posixPath string) {
|
||||||
|
// volume should be encoded at the start (e.g /c/<path>) where c is the volume
|
||||||
|
volumeName := filepath.VolumeName(windowsPath)
|
||||||
|
pathWithoutVolume := strings.TrimPrefix(windowsPath, volumeName)
|
||||||
|
volumeLetter := strings.ToLower(strings.TrimSuffix(volumeName, ":"))
|
||||||
|
|
||||||
|
// translate non-escaped backslash to forwardslash
|
||||||
|
translatedPath := strings.ReplaceAll(pathWithoutVolume, "\\", "/")
|
||||||
|
|
||||||
|
// always have `/` as the root... join all components, e.g.:
|
||||||
|
// convert: C:\\some\windows\Place
|
||||||
|
// into: /c/some/windows/Place
|
||||||
|
return path.Clean("/" + strings.Join([]string{volumeLetter, translatedPath}, "/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func posixToWindows(posixPath string) (windowsPath string) {
|
||||||
|
// decode the volume (e.g. /c/<path> --> C:\\) - There should always be a volume name.
|
||||||
|
pathFields := strings.Split(posixPath, "/")
|
||||||
|
volumeName := strings.ToUpper(pathFields[1]) + `:\\`
|
||||||
|
|
||||||
|
// translate non-escaped forward slashes into backslashes
|
||||||
|
remainingTranslatedPath := strings.Join(pathFields[2:], "\\")
|
||||||
|
|
||||||
|
// combine volume name and backslash components
|
||||||
|
return filepath.Clean(volumeName + remainingTranslatedPath)
|
||||||
|
}
|
||||||
|
|
||||||
func isUnixSystemRuntimePath(path string, _ os.FileInfo) bool {
|
func isUnixSystemRuntimePath(path string, _ os.FileInfo) bool {
|
||||||
return internal.HasAnyOfPrefixes(path, unixSystemRuntimePrefixes...)
|
return internal.HasAnyOfPrefixes(path, unixSystemRuntimePrefixes...)
|
||||||
}
|
}
|
||||||
@ -421,8 +471,8 @@ func isUnallowableFileType(_ string, info os.FileInfo) bool {
|
|||||||
switch newFileTypeFromMode(info.Mode()) {
|
switch newFileTypeFromMode(info.Mode()) {
|
||||||
case CharacterDevice, Socket, BlockDevice, FIFONode, IrregularFile:
|
case CharacterDevice, Socket, BlockDevice, FIFONode, IrregularFile:
|
||||||
return true
|
return true
|
||||||
// note: symlinks that point to these files may still get by. We handle this later in processing to help prevent
|
// note: symlinks that point to these files may still get by.
|
||||||
// against infinite links traversal.
|
// We handle this later in processing to help prevent against infinite links traversal.
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -175,12 +178,12 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
|
|||||||
hasPath := resolver.HasPath(c.input)
|
hasPath := resolver.HasPath(c.input)
|
||||||
if !c.forcePositiveHasPath {
|
if !c.forcePositiveHasPath {
|
||||||
if c.refCount != 0 && !hasPath {
|
if c.refCount != 0 && !hasPath {
|
||||||
t.Errorf("expected HasPath() to indicate existance, but did not")
|
t.Errorf("expected HasPath() to indicate existence, but did not")
|
||||||
} else if c.refCount == 0 && hasPath {
|
} else if c.refCount == 0 && hasPath {
|
||||||
t.Errorf("expeced HasPath() to NOT indicate existance, but does")
|
t.Errorf("expected HasPath() to NOT indicate existence, but does")
|
||||||
}
|
}
|
||||||
} else if !hasPath {
|
} else if !hasPath {
|
||||||
t.Errorf("expected HasPath() to indicate existance, but did not (force path)")
|
t.Errorf("expected HasPath() to indicate existence, but did not (force path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := resolver.FilesByPath(c.input)
|
refs, err := resolver.FilesByPath(c.input)
|
||||||
@ -553,7 +556,6 @@ func Test_indexAllRoots(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
fixturePath string
|
fixturePath string
|
||||||
mimeType string
|
mimeType string
|
||||||
@ -567,7 +569,6 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.fixturePath, func(t *testing.T) {
|
t.Run(test.fixturePath, func(t *testing.T) {
|
||||||
|
|
||||||
resolver, err := newDirectoryResolver(test.fixturePath)
|
resolver, err := newDirectoryResolver(test.fixturePath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
locations, err := resolver.FilesByMIMEType(test.mimeType)
|
locations, err := resolver.FilesByMIMEType(test.mimeType)
|
||||||
@ -648,7 +649,6 @@ func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
|
|||||||
locations, err = resolver.FilesByPath("./link_to_link_to_readme")
|
locations, err = resolver.FilesByPath("./link_to_link_to_readme")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, locations, 1)
|
assert.Len(t, locations, 1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
|
func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
|
||||||
@ -683,10 +683,9 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
|
|||||||
if test.err {
|
if test.err {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
if test.expects != "" {
|
if test.expects != "" {
|
||||||
b, err := ioutil.ReadAll(actual)
|
b, err := ioutil.ReadAll(actual)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
126
syft/source/directory_resolver_windows_test.go
Normal file
126
syft/source/directory_resolver_windows_test.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_windowsToPosix(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
windowsPath string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantPosixPath string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic case",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:\some\windows\place`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/some/windows/place",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escaped case",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:\\some\\windows\\place`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/some/windows/place",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forward slash",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:/foo/bar`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mix slash",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:\foo/bar\`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case sensitive case",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:\Foo/bAr\`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/Foo/bAr",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "special char case",
|
||||||
|
args: args{
|
||||||
|
windowsPath: `C:\ふー\バー`,
|
||||||
|
},
|
||||||
|
wantPosixPath: "/c/ふー/バー",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if gotPosixPath := windowsToPosix(tt.args.windowsPath); gotPosixPath != tt.wantPosixPath {
|
||||||
|
t.Errorf("windowsToPosix() = %v, want %v", gotPosixPath, tt.wantPosixPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_posixToWindows(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
posixPath string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantWindowsPath string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic case",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/some/windows/place",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:\some\windows\place`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escaped case",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/some/windows/place",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:\\some\\windows\\place`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forward slash",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/foo/bar",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:/foo/bar`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mix slash",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/foo/bar",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:\foo/bar\`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "case sensitive case",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/Foo/bAr",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:\Foo/bAr\`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "special char case",
|
||||||
|
args: args{
|
||||||
|
posixPath: "/c/ふー/バー",
|
||||||
|
},
|
||||||
|
wantWindowsPath: `C:\ふー\バー`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if gotWindowsPath := posixToWindows(tt.args.posixPath); gotWindowsPath != tt.wantWindowsPath {
|
||||||
|
t.Errorf("posixToWindows() = %v, want %v", gotWindowsPath, tt.wantWindowsPath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user