Skip to content

Commit 68cb755

Browse files
path fix
1 parent 354dbca commit 68cb755

File tree

2 files changed

+196
-24
lines changed

2 files changed

+196
-24
lines changed

internal/commands/scan.go

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,20 +2309,11 @@ func enforceLocalResolutionForTarFiles(cmd *cobra.Command) error {
23092309
}
23102310

23112311
// isTarFileReference checks if a container image reference points to a tar file.
2312-
// Container-security scan-type related function.
2312+
// Handles both Unix and Windows paths (e.g., C:\path\file.tar).
23132313
func isTarFileReference(imageRef string) bool {
2314-
// Known prefixes that might precede the actual file path
2315-
knownPrefixes := []string{
2316-
dockerArchivePrefix,
2317-
ociArchivePrefix,
2318-
filePrefix,
2319-
ociDirPrefix,
2320-
}
2314+
knownPrefixes := []string{dockerArchivePrefix, ociArchivePrefix, filePrefix, ociDirPrefix}
23212315

2322-
// First, trim quotes from the entire input
23232316
actualRef := strings.Trim(imageRef, "'\"")
2324-
2325-
// Strip known prefixes to get the actual reference
23262317
for _, prefix := range knownPrefixes {
23272318
if strings.HasPrefix(actualRef, prefix) {
23282319
actualRef = strings.TrimPrefix(actualRef, prefix)
@@ -2331,31 +2322,35 @@ func isTarFileReference(imageRef string) bool {
23312322
}
23322323
}
23332324

2334-
// Check if the reference ends with .tar (case-insensitive)
23352325
lowerRef := strings.ToLower(actualRef)
2336-
2337-
// If it ends with .tar, it's a tar file (no tag suffix allowed)
23382326
if strings.HasSuffix(lowerRef, ".tar") {
23392327
return true
23402328
}
23412329

2342-
// If it contains a colon but doesn't end with .tar, check if it's a file.tar:tag format (invalid)
2343-
// A tar file cannot have a tag suffix like file.tar:tag
2330+
if isWindowsAbsolutePath(actualRef) {
2331+
return strings.Contains(lowerRef, ".tar")
2332+
}
2333+
23442334
if strings.Contains(actualRef, ":") {
23452335
parts := strings.Split(actualRef, ":")
2346-
const minPartsForTaggedImage = 2
2347-
if len(parts) >= minPartsForTaggedImage {
2348-
firstPart := strings.ToLower(parts[0])
2349-
// If the part before the colon is a tar file, this is invalid (tar files don't have tags)
2350-
if strings.HasSuffix(firstPart, ".tar") {
2351-
return false
2352-
}
2336+
if len(parts) >= 2 && strings.HasSuffix(strings.ToLower(parts[0]), ".tar") {
2337+
return false
23532338
}
23542339
}
23552340

23562341
return false
23572342
}
23582343

2344+
// isWindowsAbsolutePath checks for Windows drive letter paths (e.g., C:\, D:/).
2345+
func isWindowsAbsolutePath(path string) bool {
2346+
if len(path) < 3 {
2347+
return false
2348+
}
2349+
firstChar := path[0]
2350+
isLetter := (firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z')
2351+
return isLetter && path[1] == ':' && (path[2] == '\\' || path[2] == '/')
2352+
}
2353+
23592354
func runCreateScanCommand(
23602355
scansWrapper wrappers.ScansWrapper,
23612356
exportWrapper wrappers.ExportWrapper,
@@ -3573,7 +3568,7 @@ const (
35733568
// Container-security scan-type related function.
35743569
// This function implements comprehensive validation logic for all supported container image formats:
35753570
// - Standard image:tag format
3576-
// - Tar files (.tar)
3571+
// - Tar files (.tar) - including full file paths on Windows (C:\path\file.tar) and Unix (/path/file.tar)
35773572
// - Prefixed formats (docker:, podman:, containerd:, registry:, docker-archive:, oci-archive:, oci-dir:, file:)
35783573
// It provides helpful error messages and hints for common user mistakes.
35793574
func validateContainerImageFormat(containerImage string) error {
@@ -3613,6 +3608,11 @@ func validateContainerImageFormat(containerImage string) error {
36133608
sanitizedInput = containerImage
36143609
}
36153610

3611+
// Check if this looks like a file path before parsing colons
3612+
if looksLikeFilePath(sanitizedInput) {
3613+
return validateFilePath(sanitizedInput)
3614+
}
3615+
36163616
// Step 2: Look for the last colon (:) in the sanitized input
36173617
lastColonIndex := strings.LastIndex(sanitizedInput, ":")
36183618

@@ -3684,6 +3684,63 @@ func validateContainerImageFormat(containerImage string) error {
36843684
return errors.Errorf("--container-images flag error: image does not have a tag")
36853685
}
36863686

3687+
// looksLikeFilePath checks if input looks like a file path rather than image:tag.
3688+
func looksLikeFilePath(input string) bool {
3689+
lowerInput := strings.ToLower(input)
3690+
3691+
if isWindowsAbsolutePath(input) {
3692+
return true
3693+
}
3694+
3695+
// If colon exists and part before it looks like a prefix (no separators/dots), it's not a file path
3696+
if colonIndex := strings.Index(input, ":"); colonIndex > 0 {
3697+
beforeColon := input[:colonIndex]
3698+
if !strings.Contains(beforeColon, "/") && !strings.Contains(beforeColon, "\\") && !strings.Contains(beforeColon, ".") {
3699+
return false
3700+
}
3701+
}
3702+
3703+
if strings.HasSuffix(lowerInput, ".tar") {
3704+
return true
3705+
}
3706+
3707+
if strings.HasSuffix(lowerInput, ".tar.gz") || strings.HasSuffix(lowerInput, ".tar.bz2") ||
3708+
strings.HasSuffix(lowerInput, ".tar.xz") || strings.HasSuffix(lowerInput, ".tgz") {
3709+
return true
3710+
}
3711+
3712+
hasPathSeparators := strings.Contains(input, "/") || strings.Contains(input, "\\")
3713+
if hasPathSeparators && strings.Contains(lowerInput, ".tar") {
3714+
return true
3715+
}
3716+
3717+
return false
3718+
}
3719+
3720+
// validateFilePath validates file path input for tar files.
3721+
func validateFilePath(filePath string) error {
3722+
lowerPath := strings.ToLower(filePath)
3723+
3724+
if strings.HasSuffix(lowerPath, ".tar.gz") || strings.HasSuffix(lowerPath, ".tar.bz2") ||
3725+
strings.HasSuffix(lowerPath, ".tar.xz") || strings.HasSuffix(lowerPath, ".tgz") {
3726+
return errors.Errorf("--container-images flag error: file '%s' is compressed, use non-compressed format (tar)", filePath)
3727+
}
3728+
3729+
if !strings.HasSuffix(lowerPath, ".tar") {
3730+
return errors.Errorf("--container-images flag error: file '%s' is not a valid tar file. Expected .tar extension", filePath)
3731+
}
3732+
3733+
exists, err := osinstaller.FileExists(filePath)
3734+
if err != nil {
3735+
return errors.Errorf("--container-images flag error: %v", err)
3736+
}
3737+
if !exists {
3738+
return errors.Errorf("--container-images flag error: file '%s' does not exist", filePath)
3739+
}
3740+
3741+
return nil
3742+
}
3743+
36873744
// getPrefixFromInput extracts the prefix from a container image reference.
36883745
// Container-security scan-type related function.
36893746
// Helper function to identify which known prefix is used in the input.

internal/commands/scan_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,91 @@ func Test_validateThresholds(t *testing.T) {
24012401

24022402
// TestValidateContainerImageFormat_Comprehensive tests the complete validation logic
24032403
// including input normalization, helpful hints, and all error cases.
2404+
// TestIsWindowsAbsolutePath tests the Windows absolute path detection.
2405+
// Container-security scan-type related test function.
2406+
func TestIsWindowsAbsolutePath(t *testing.T) {
2407+
testCases := []struct {
2408+
name string
2409+
input string
2410+
expected bool
2411+
}{
2412+
// Valid Windows absolute paths
2413+
{name: "C drive with backslash", input: "C:\\Users\\file.tar", expected: true},
2414+
{name: "D drive with backslash", input: "D:\\data\\image.tar", expected: true},
2415+
{name: "C drive with forward slash", input: "C:/Users/file.tar", expected: true},
2416+
{name: "Lowercase drive letter", input: "c:\\path\\file.tar", expected: true},
2417+
2418+
// Not Windows absolute paths
2419+
{name: "Unix absolute path", input: "/path/to/file.tar", expected: false},
2420+
{name: "Relative path", input: "Downloads/file.tar", expected: false},
2421+
{name: "Simple filename", input: "file.tar", expected: false},
2422+
{name: "Image with tag", input: "nginx:latest", expected: false},
2423+
{name: "Too short", input: "C:", expected: false},
2424+
{name: "No path separator after colon", input: "C:file.tar", expected: false},
2425+
}
2426+
2427+
for _, tc := range testCases {
2428+
tc := tc
2429+
t.Run(tc.name, func(t *testing.T) {
2430+
result := isWindowsAbsolutePath(tc.input)
2431+
if result != tc.expected {
2432+
t.Errorf("isWindowsAbsolutePath(%q) = %v, expected %v", tc.input, result, tc.expected)
2433+
}
2434+
})
2435+
}
2436+
}
2437+
2438+
// TestLooksLikeFilePath tests the file path detection logic for cross-platform support.
2439+
// Container-security scan-type related test function.
2440+
// This test validates the looksLikeFilePath function for various Windows and Unix path formats.
2441+
func TestLooksLikeFilePath(t *testing.T) {
2442+
testCases := []struct {
2443+
name string
2444+
input string
2445+
expected bool
2446+
}{
2447+
// Tar file extensions
2448+
{name: "Simple tar file", input: "image.tar", expected: true},
2449+
{name: "Tar.gz file", input: "image.tar.gz", expected: true},
2450+
{name: "Tar.bz2 file", input: "image.tar.bz2", expected: true},
2451+
{name: "Tar.xz file", input: "image.tar.xz", expected: true},
2452+
{name: "Tgz file", input: "image.tgz", expected: true},
2453+
2454+
// Unix-style paths
2455+
{name: "Unix relative path with tar", input: "subdir/image.tar", expected: true},
2456+
{name: "Unix absolute path with tar", input: "/path/to/image.tar", expected: true},
2457+
{name: "Unix path with version in filename", input: "Downloads/alpine_3.21.0_podman.tar", expected: true},
2458+
{name: "Unix nested path", input: "path/to/nested/dir/file.tar", expected: true},
2459+
2460+
// Windows-style paths
2461+
{name: "Windows absolute path with drive letter", input: "C:\\Users\\Downloads\\image.tar", expected: true},
2462+
{name: "Windows path with forward slash after drive", input: "C:/Users/Downloads/image.tar", expected: true},
2463+
{name: "Windows relative path with backslash", input: "Downloads\\alpine_3.21.0_podman.tar", expected: true},
2464+
{name: "Windows D drive path", input: "D:\\data\\images\\test.tar", expected: true},
2465+
2466+
// Not file paths (image:tag format)
2467+
{name: "Simple image:tag", input: "nginx:latest", expected: false},
2468+
{name: "Image with registry", input: "registry.io/namespace/image:tag", expected: false},
2469+
{name: "Image with port", input: "registry.io:5000/image:tag", expected: false},
2470+
{name: "Image without tag", input: "nginx", expected: false},
2471+
2472+
// Edge cases
2473+
{name: "Tar file with dots in name", input: "alpine.3.18.0.tar", expected: true},
2474+
{name: "Tar file with version like name", input: "app_v1.2.3.tar", expected: true},
2475+
{name: "Path with tar in middle", input: "tarball/other.tar", expected: true},
2476+
}
2477+
2478+
for _, tc := range testCases {
2479+
tc := tc
2480+
t.Run(tc.name, func(t *testing.T) {
2481+
result := looksLikeFilePath(tc.input)
2482+
if result != tc.expected {
2483+
t.Errorf("looksLikeFilePath(%q) = %v, expected %v", tc.input, result, tc.expected)
2484+
}
2485+
})
2486+
}
2487+
}
2488+
24042489
// Container-security scan-type related test function.
24052490
// This test validates all supported container image formats, prefixes, tar files,
24062491
// error messages, and helpful hints for the --container-images flag.
@@ -2487,6 +2572,36 @@ func TestValidateContainerImageFormat_Comprehensive(t *testing.T) {
24872572
expectedError: "--container-images flag error: file 'image.tgz' is compressed, use non-compressed format (tar)",
24882573
},
24892574

2575+
// ==================== File Path Tests (Windows and Unix) ====================
2576+
// Note: These tests validate that path-like inputs are correctly recognized as file paths
2577+
{
2578+
name: "Valid tar file with filename containing version number",
2579+
containerImage: "alpine_3.21.0_podman.tar",
2580+
expectedError: "",
2581+
setupFiles: []string{"alpine_3.21.0_podman.tar"},
2582+
},
2583+
{
2584+
name: "Valid tar file with filename containing underscore and version",
2585+
containerImage: "mysql_5.7_backup.tar",
2586+
expectedError: "",
2587+
setupFiles: []string{"mysql_5.7_backup.tar"},
2588+
},
2589+
{
2590+
name: "Invalid - Unix relative path does not exist",
2591+
containerImage: "subdir/image.tar",
2592+
expectedError: "--container-images flag error: file 'subdir/image.tar' does not exist",
2593+
},
2594+
{
2595+
name: "Invalid - Unix nested path does not exist",
2596+
containerImage: "path/to/archive/my-image.tar",
2597+
expectedError: "--container-images flag error: file 'path/to/archive/my-image.tar' does not exist",
2598+
},
2599+
{
2600+
name: "Invalid - file path with version-like name does not exist",
2601+
containerImage: "Downloads/alpine_3.21.0_podman.tar",
2602+
expectedError: "--container-images flag error: file 'Downloads/alpine_3.21.0_podman.tar' does not exist",
2603+
},
2604+
24902605
// ==================== Helpful Hints Tests ====================
24912606
{
24922607
name: "Hint - looks like tar file (wrong extension)",

0 commit comments

Comments
 (0)