Skip to content

Commit a4d1c04

Browse files
Added code for .gitignore file filter
1 parent 1060626 commit a4d1c04

File tree

4 files changed

+379
-0
lines changed

4 files changed

+379
-0
lines changed

internal/commands/scan.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ func scanCreateSubCommand(
870870

871871
// reading sbom-only flag
872872
createScanCmd.PersistentFlags().Bool(commonParams.SbomFlag, false, "Scan only the specified SBOM file (supported formats xml or json)")
873+
createScanCmd.PersistentFlags().Bool(commonParams.GitIgnoreFileFilterFlag, false, commonParams.GitIgnoreFileFilterUsage)
873874

874875
return createScanCmd
875876
}
@@ -1747,6 +1748,7 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17471748

17481749
scaResolverParams, scaResolver := getScaResolverFlags(cmd)
17491750
isSbom, _ := cmd.PersistentFlags().GetBool(commonParams.SbomFlag)
1751+
isGitIgnoreFilter, _ := cmd.Flags().GetBool(commonParams.GitIgnoreFileFilterFlag)
17501752
var directoryPath string
17511753
if isSbom {
17521754
sbomFile, _ := cmd.Flags().GetString(commonParams.SourcesFlag)
@@ -1763,6 +1765,29 @@ func getUploadURLFromSource(cmd *cobra.Command, uploadsWrapper wrappers.UploadsW
17631765
}
17641766
} else {
17651767
zipFilePath, directoryPath, err = definePathForZipFileOrDirectory(cmd)
1768+
if isGitIgnoreFilter {
1769+
gitIgnoreFilter, err := getGitignorePatterns(directoryPath, zipFilePath)
1770+
if err != nil {
1771+
return "", "", err
1772+
}
1773+
1774+
if len(gitIgnoreFilter) > 0 {
1775+
if sourceDirFilter == "" {
1776+
sourceDirFilter = gitIgnoreFilter[0]
1777+
for i := 1; i < len(gitIgnoreFilter); i++ {
1778+
if !strings.Contains(sourceDirFilter, gitIgnoreFilter[i]) {
1779+
sourceDirFilter += "," + gitIgnoreFilter[i]
1780+
}
1781+
}
1782+
} else {
1783+
for _, pattern := range gitIgnoreFilter {
1784+
if !strings.Contains(sourceDirFilter, pattern) {
1785+
sourceDirFilter += "," + pattern
1786+
}
1787+
}
1788+
}
1789+
}
1790+
}
17661791
}
17671792

17681793
if zipFilePath != "" && scaResolverPath != "" {
@@ -3249,3 +3274,89 @@ func isValidJSONOrXML(path string) (bool, error) {
32493274

32503275
return true, nil
32513276
}
3277+
3278+
func getGitignorePatterns(directoryPath, zipFilePath string) ([]string, error) {
3279+
var data []byte
3280+
var err error
3281+
if directoryPath != "" {
3282+
gitignorePath := filepath.Join(directoryPath, ".gitignore")
3283+
if _, err := os.Stat(gitignorePath); os.IsNotExist(err) {
3284+
return nil, fmt.Errorf(".gitignore file not found in directory: %s", directoryPath)
3285+
}
3286+
data, err = os.ReadFile(gitignorePath)
3287+
if err != nil {
3288+
return nil, err
3289+
}
3290+
}
3291+
if zipFilePath != "" {
3292+
data, err = readGitIgnoreFromZip(zipFilePath)
3293+
if err != nil {
3294+
return nil, err
3295+
}
3296+
}
3297+
lines := strings.Split(string(data), "\n")
3298+
var patterns []string
3299+
for _, line := range lines {
3300+
line = strings.TrimSpace(line)
3301+
3302+
//This condition skips lines that are empty, comments.
3303+
// Excluding the lines that contain negotiation characters like !, which are used to negate patterns
3304+
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "!") {
3305+
continue
3306+
}
3307+
// Convert build/** to build for path.Match() supported patterns
3308+
if strings.HasSuffix(line, "/**") {
3309+
line = strings.TrimSuffix(line, "/**")
3310+
}
3311+
// Convert **/temp/ to temp for path.Match() supported patterns
3312+
if strings.HasPrefix(line, "**/") {
3313+
line = strings.TrimPrefix(line, "**/")
3314+
}
3315+
// Convert temp/ to temp for path.Match() supported patterns
3316+
if strings.HasSuffix(line, "/") {
3317+
line = strings.TrimSuffix(line, "/")
3318+
}
3319+
3320+
// Convert LoginController[!0-3].java to LoginController[^0-3].java for path.Match() supported patterns
3321+
if strings.Contains(line, "!") {
3322+
line = strings.ReplaceAll(line, "!", "^")
3323+
}
3324+
patterns = append(patterns, "!"+line)
3325+
}
3326+
return patterns, nil
3327+
}
3328+
3329+
func readGitIgnoreFromZip(zipPath string) ([]byte, error) {
3330+
r, err := zip.OpenReader(zipPath)
3331+
if err != nil {
3332+
return []byte(""), fmt.Errorf("failed to open zip: %s", zipPath)
3333+
}
3334+
defer r.Close()
3335+
3336+
rootFolder := ""
3337+
if len(r.File) > 0 {
3338+
parts := strings.Split(r.File[0].Name, "/")
3339+
if len(parts) > 1 {
3340+
rootFolder = parts[0]
3341+
}
3342+
}
3343+
expectedGitignorePath := rootFolder + "/.gitignore"
3344+
3345+
for _, f := range r.File {
3346+
if f.Name == expectedGitignorePath {
3347+
rc, err := f.Open()
3348+
if err != nil {
3349+
return []byte(""), fmt.Errorf("failed to open .gitignore inside zip: %w", err)
3350+
}
3351+
defer rc.Close()
3352+
3353+
// Read file content
3354+
data, err := io.ReadAll(rc)
3355+
if err != nil {
3356+
return []byte(""), fmt.Errorf("failed to read .gitignore content inside zip : %w", err)
3357+
}
3358+
return data, nil
3359+
}
3360+
}
3361+
return []byte(""), fmt.Errorf(".gitignore not found in zip: %s", zipPath)
3362+
}

internal/commands/scan_test.go

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"log"
1111
"os"
12+
"path/filepath"
1213
"reflect"
1314
"strings"
1415
"testing"
@@ -2544,3 +2545,255 @@ func Test_CreateScanWithSbomFlag(t *testing.T) {
25442545

25452546
assert.ErrorContains(t, err, "Failed creating a scan: Input in bad format: failed to read file:")
25462547
}
2548+
2549+
func TestGetGitignorePatterns_DirPath_GitIgnore_NotFound(t *testing.T) {
2550+
dir := t.TempDir()
2551+
_, err := getGitignorePatterns(dir, "")
2552+
assert.ErrorContains(t, err, ".gitignore file not found in directory")
2553+
}
2554+
2555+
func TestGetGitignorePatterns_DirPath_GitIgnore_PermissionDenied(t *testing.T) {
2556+
dir := t.TempDir()
2557+
err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(""), 0000)
2558+
if err != nil {
2559+
t.Fatalf("Failed to write .gitignore: %v", err)
2560+
}
2561+
_, err = getGitignorePatterns(dir, "")
2562+
assert.ErrorContains(t, err, "permission denied")
2563+
}
2564+
2565+
func TestGetGitignorePatterns_DirPath_GitIgnore_EmptyPatternList(t *testing.T) {
2566+
dir := t.TempDir()
2567+
err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(""), 0644)
2568+
if err != nil {
2569+
t.Fatalf("Failed to write .gitignore: %v", err)
2570+
}
2571+
gitIgnoreFilter, err := getGitignorePatterns(dir, "")
2572+
if err != nil {
2573+
t.Fatalf("Error in fetching pattern from .gitignore file: %v", err)
2574+
}
2575+
assert.Assert(t, len(gitIgnoreFilter) == 0, "Expected no patterns from empty .gitignore file")
2576+
}
2577+
2578+
func TestGetGitignorePatterns_DirPath_GitIgnore_PatternList(t *testing.T) {
2579+
dir := t.TempDir()
2580+
gitignoreContent := `src
2581+
src/
2582+
**/vullib
2583+
**/admin/
2584+
vulnerability/**
2585+
application-jira.yml
2586+
*.yml
2587+
LoginController[0-1].java
2588+
LoginController[!0-3].java
2589+
LoginController[01].java
2590+
LoginController[!456].java
2591+
?pplication-jira.yml
2592+
a*cation-jira.yml`
2593+
err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(gitignoreContent), 0644)
2594+
if err != nil {
2595+
t.Fatalf("Failed to write .gitignore: %v", err)
2596+
}
2597+
gitIgnoreFilter, err := getGitignorePatterns(dir, "")
2598+
if err != nil {
2599+
t.Fatalf("Error in fetching pattern from .gitignore file: %v", err)
2600+
}
2601+
assert.Assert(t, len(gitIgnoreFilter) > 0, "Expected patterns from .gitignore file")
2602+
}
2603+
2604+
func TestGetGitignorePatterns_ZipPath_GitIgnore_FailedToOpenZipFIle(t *testing.T) {
2605+
dir := t.TempDir()
2606+
zipPath := filepath.Join(dir, "example.zip")
2607+
2608+
// Create the zip file
2609+
zipFile, err := os.Create(zipPath)
2610+
if err != nil {
2611+
t.Fatalf("Failed to create zip file: %v", err)
2612+
}
2613+
defer func(zipFile *os.File) {
2614+
err := zipFile.Close()
2615+
if err != nil {
2616+
t.Fatalf("Failed to close zip file: %v", err)
2617+
}
2618+
}(zipFile)
2619+
_, err = getGitignorePatterns("", zipPath)
2620+
assert.ErrorContains(t, err, "failed to open zip")
2621+
}
2622+
2623+
func TestGetGitignorePatterns_ZipPath_GitIgnore_NotFound(t *testing.T) {
2624+
dir := t.TempDir()
2625+
zipPath := filepath.Join(dir, "example.zip")
2626+
2627+
// Create the zip file
2628+
zipFile, err := os.Create(zipPath)
2629+
if err != nil {
2630+
t.Fatalf("Failed to create zip file: %v", err)
2631+
}
2632+
defer func(zipFile *os.File) {
2633+
err := zipFile.Close()
2634+
if err != nil {
2635+
t.Fatalf("Failed to close zip file: %v", err)
2636+
}
2637+
}(zipFile)
2638+
2639+
// Create a zip writer
2640+
zipWriter := zip.NewWriter(zipFile)
2641+
err = zipWriter.Close()
2642+
if err != nil {
2643+
return
2644+
}
2645+
2646+
_, err = getGitignorePatterns("", zipPath)
2647+
assert.ErrorContains(t, err, ".gitignore not found in zip")
2648+
}
2649+
2650+
func TestGetGitignorePatterns_ZipPath_GitIgnore_EmptyPatternList(t *testing.T) {
2651+
dir := t.TempDir()
2652+
zipPath := filepath.Join(dir, "example.zip")
2653+
2654+
// Create the zip file
2655+
zipFile, err := os.Create(zipPath)
2656+
if err != nil {
2657+
t.Fatalf("Failed to create zip file: %v", err)
2658+
}
2659+
defer func(zipFile *os.File) {
2660+
err := zipFile.Close()
2661+
if err != nil {
2662+
t.Fatalf("Failed to close zip file: %v", err)
2663+
}
2664+
}(zipFile)
2665+
2666+
// Create a zip writer
2667+
zipWriter := zip.NewWriter(zipFile)
2668+
2669+
// Add a file to the zip archive
2670+
fileInZip, err := zipWriter.Create("example" + "/.gitignore")
2671+
if err != nil {
2672+
t.Fatalf("Failed to add file to zip: %v", err)
2673+
}
2674+
2675+
_, err = fileInZip.Write([]byte(""))
2676+
if err != nil {
2677+
t.Fatalf("Failed to write data to zip: %v", err)
2678+
}
2679+
err = zipWriter.Close()
2680+
if err != nil {
2681+
return
2682+
}
2683+
2684+
gitIgnoreFilter, err := getGitignorePatterns("", zipPath)
2685+
assert.Assert(t, len(gitIgnoreFilter) == 0, "Expected no patterns from empty .gitignore file")
2686+
2687+
}
2688+
2689+
func TestGetGitignorePatterns_ZipPath_GitIgnore_PatternList(t *testing.T) {
2690+
dir := t.TempDir()
2691+
zipPath := filepath.Join(dir, "example.zip")
2692+
2693+
// Create the zip file
2694+
zipFile, err := os.Create(zipPath)
2695+
if err != nil {
2696+
t.Fatalf("Failed to create zip file: %v", err)
2697+
}
2698+
defer func(zipFile *os.File) {
2699+
err := zipFile.Close()
2700+
if err != nil {
2701+
t.Fatalf("Failed to close zip file: %v", err)
2702+
}
2703+
}(zipFile)
2704+
2705+
// Create a zip writer
2706+
zipWriter := zip.NewWriter(zipFile)
2707+
2708+
// Add a file to the zip archive
2709+
fileInZip, err := zipWriter.Create("example" + "/.gitignore")
2710+
if err != nil {
2711+
t.Fatalf("Failed to add file to zip: %v", err)
2712+
}
2713+
2714+
gitignoreContent := `src
2715+
src/
2716+
**/vullib
2717+
**/admin/
2718+
vulnerability/**
2719+
application-jira.yml
2720+
*.yml
2721+
LoginController[0-1].java
2722+
LoginController[!0-3].java
2723+
LoginController[01].java
2724+
LoginController[!456].java
2725+
?pplication-jira.yml
2726+
a*cation-jira.yml`
2727+
_, err = fileInZip.Write([]byte(gitignoreContent))
2728+
if err != nil {
2729+
t.Fatalf("Failed to write data to zip: %v", err)
2730+
}
2731+
err = zipWriter.Close()
2732+
if err != nil {
2733+
return
2734+
}
2735+
2736+
gitIgnoreFilter, err := getGitignorePatterns("", zipPath)
2737+
assert.Assert(t, len(gitIgnoreFilter) > 0, "Expected patterns from .gitignore file")
2738+
2739+
}
2740+
2741+
func TestGetGitignorePatterns_ZipPath_GitIgnore_PermissionDenied(t *testing.T) {
2742+
dir := t.TempDir()
2743+
zipPath := filepath.Join(dir, "example.zip")
2744+
2745+
// Create the zip file
2746+
zipFile, err := os.Create(zipPath)
2747+
if err != nil {
2748+
t.Fatalf("Failed to create zip file: %v", err)
2749+
}
2750+
defer func(zipFile *os.File) {
2751+
err := zipFile.Close()
2752+
if err != nil {
2753+
t.Fatalf("Failed to close zip file: %v", err)
2754+
}
2755+
}(zipFile)
2756+
2757+
// Create a zip writer
2758+
zipWriter := zip.NewWriter(zipFile)
2759+
2760+
// Set up a header with 0000 permission for .gitignore
2761+
header := &zip.FileHeader{
2762+
Name: "example/.gitignore",
2763+
Method: zip.Deflate,
2764+
}
2765+
header.SetMode(0) // UNIX permission 0000 — no access for anyone
2766+
2767+
// Add a file to the zip archive
2768+
fileInZip, err := zipWriter.CreateHeader(header)
2769+
if err != nil {
2770+
t.Fatalf("Failed to add file to zip: %v", err)
2771+
}
2772+
2773+
gitignoreContent := `src
2774+
src/
2775+
**/vullib
2776+
**/admin/
2777+
vulnerability/**
2778+
application-jira.yml
2779+
*.yml
2780+
LoginController[0-1].java
2781+
LoginController[!0-3].java
2782+
LoginController[01].java
2783+
LoginController[!456].java
2784+
?pplication-jira.yml
2785+
a*cation-jira.yml`
2786+
2787+
// Write content to the file in the zip archive
2788+
_, err = fileInZip.Write([]byte(gitignoreContent))
2789+
if err != nil {
2790+
t.Fatalf("Failed to write data to zip: %v", err)
2791+
}
2792+
err = zipWriter.Close()
2793+
if err != nil {
2794+
return
2795+
}
2796+
2797+
_, err = getGitignorePatterns("", zipPath)
2798+
assert.ErrorContains(t, err, "permission denied")
2799+
}

0 commit comments

Comments
 (0)