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:
Christopher Angelo Phillips 2022-01-06 11:39:04 -05:00 committed by GitHub
parent 2a7325a965
commit 01dc78ccc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 209 additions and 11 deletions

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package file
import (

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package file
import (

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package file
import (

View File

@ -2,6 +2,7 @@ package testutils
import (
"bytes"
"strings"
"testing"
"github.com/anchore/go-presenter"
@ -51,6 +52,7 @@ func AssertPresenterAgainstGoldenImageSnapshot(t *testing.T, pres presenter.Pres
var expected = testutils.GetGoldenFileContents(t)
// remove dynamic values, which should be tested independently
redactors = append(redactors, carriageRedactor)
for _, r := range redactors {
actual = r(actual)
expected = r(expected)
@ -79,6 +81,7 @@ func AssertPresenterAgainstGoldenSnapshot(t *testing.T, pres presenter.Presenter
var expected = testutils.GetGoldenFileContents(t)
// remove dynamic values, which should be tested independently
redactors = append(redactors, carriageRedactor)
for _, r := range redactors {
actual = r(actual)
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) {
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)

View File

@ -8,6 +8,7 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/anchore/syft/internal"
@ -21,6 +22,8 @@ import (
"github.com/wagoodman/go-progress"
)
const WindowsOS = "windows"
var unixSystemRuntimePrefixes = []string{
"/proc",
"/dev",
@ -143,6 +146,11 @@ func (r *directoryResolver) indexPath(path string, info os.FileInfo, err error)
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)
if r.isFileAccessErr(path, err) {
return "", nil
@ -258,6 +266,11 @@ func (r directoryResolver) requestPath(userPath string) (string, error) {
}
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)
if filepath.IsAbs(path) {
// 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
}
if runtime.GOOS == WindowsOS {
userStrPath = windowsToPosix(userStrPath)
}
exists, ref, err := r.fileTree.File(file.Path(userStrPath))
if err == nil && exists {
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).
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 {
@ -409,6 +432,33 @@ func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error)
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 {
return internal.HasAnyOfPrefixes(path, unixSystemRuntimePrefixes...)
}
@ -421,8 +471,8 @@ func isUnallowableFileType(_ string, info os.FileInfo) bool {
switch newFileTypeFromMode(info.Mode()) {
case CharacterDevice, Socket, BlockDevice, FIFONode, IrregularFile:
return true
// note: symlinks that point to these files may still get by. We handle this later in processing to help prevent
// against infinite links traversal.
// note: symlinks that point to these files may still get by.
// We handle this later in processing to help prevent against infinite links traversal.
}
return false

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package source
import (
@ -175,12 +178,12 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
hasPath := resolver.HasPath(c.input)
if !c.forcePositiveHasPath {
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 {
t.Errorf("expeced HasPath() to NOT indicate existance, but does")
t.Errorf("expected HasPath() to NOT indicate existence, but does")
}
} 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)
@ -553,7 +556,6 @@ func Test_indexAllRoots(t *testing.T) {
}
func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
tests := []struct {
fixturePath string
mimeType string
@ -567,7 +569,6 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
}
for _, test := range tests {
t.Run(test.fixturePath, func(t *testing.T) {
resolver, err := newDirectoryResolver(test.fixturePath)
assert.NoError(t, err)
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")
require.NoError(t, err)
assert.Len(t, locations, 1)
}
func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
@ -683,10 +683,9 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
if test.err {
require.Error(t, err)
return
} else {
require.NoError(t, err)
}
require.NoError(t, err)
if test.expects != "" {
b, err := ioutil.ReadAll(actual)
require.NoError(t, err)

View 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)
}
})
}
}

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package source
import (

View File

@ -1,3 +1,6 @@
//go:build !windows
// +build !windows
package source
import (