diff --git a/provider/internal/builtin/service_client.go b/provider/internal/builtin/service_client.go index b9ac4ad5..20079e5b 100644 --- a/provider/internal/builtin/service_client.go +++ b/provider/internal/builtin/service_client.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strconv" "strings" "sync" @@ -83,14 +84,12 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi if c.Pattern == "" { return response, fmt.Errorf("could not parse provided regex pattern as string: %v", conditionInfo) } + var outputBytes []byte - grep := exec.Command("grep", "-o", "-n", "-R", "-P", c.Pattern, p.config.Location) - outputBytes, err := grep.Output() + //Runs on Windows using PowerShell.exe and Unix based systems using grep + outputBytes, err := runOSSpecificGrepCommand(c.Pattern, p.config.Location) if err != nil { - if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { - return response, nil - } - return response, fmt.Errorf("could not run grep with provided pattern %+v", err) + return response, err } matches := []string{} @@ -100,13 +99,10 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi } for _, match := range matches { - //TODO(fabianvf): This will not work if there is a `:` in the filename, do we care? - pieces := strings.SplitN(match, ":", 3) - if len(pieces) != 3 { - //TODO(fabianvf): Just log or return? - //(shawn-hurley): I think the return is good personally - return response, fmt.Errorf( - "malformed response from grep, cannot parse grep output '%s' with pattern {filepath}:{lineNumber}:{matchingText}", match) + var pieces []string + pieces, err := parseGrepOutputForFileContent(match) + if err != nil { + return response, fmt.Errorf("could not parse grep output '%s' for the Pattern '%v': %v ", match, c.Pattern, err) } containsFile, err := provider.FilterFilePattern(c.FilePattern, pieces[0]) @@ -130,6 +126,7 @@ func (p *builtinServiceClient) Evaluate(ctx context.Context, cap string, conditi if err != nil { return response, fmt.Errorf("cannot convert line number string to integer") } + response.Incidents = append(response.Incidents, provider.IncidentContext{ FileURI: uri.File(absPath), LineNumber: &lineNumber, @@ -497,3 +494,60 @@ func (b *builtinServiceClient) isFileIncluded(absolutePath string) bool { b.log.V(7).Info("excluding file from search", "file", absolutePath) return false } + +func parseGrepOutputForFileContent(match string) ([]string, error) { + // This will parse the output of the PowerShell/grep in the form + // "Filepath:Linenumber:Matchingtext" to return string array of path, line number and matching text + // works with handling both windows and unix based file paths eg: "C:\path\to\file" and "/path/to/file" + re, err := regexp.Compile(`^(.*?):(\d+):(.*)$`) + if err != nil { + return nil, fmt.Errorf("failed to compile regular expression: %v", err) + } + submatches := re.FindStringSubmatch(match) + if len(submatches) != 4 { + return nil, fmt.Errorf( + "malformed response from file search, cannot parse result '%s' with pattern %#q", match, re) + } + return submatches[1:], nil +} + +func runOSSpecificGrepCommand(pattern string, location string) ([]byte, error) { + var outputBytes []byte + var err error + var utilName string + + if runtime.GOOS == "windows" { + utilName = "powershell.exe" + // Windows does not have grep, so we use PowerShell.exe's Select-String instead + // This is a workaround until we can find a better solution + psScript := ` + $pattern = $env:PATTERN + $location = $env:FILEPATH + Get-ChildItem -Path $location -Recurse -File | ForEach-Object { + $file = $_ + # Search for the pattern in the file + Select-String -Path $file.FullName -Pattern $pattern -AllMatches | ForEach-Object { + foreach ($match in $_.Matches) { + "{0}:{1}:{2}" -f $file.FullName, $_.LineNumber, $match.Value + } + } + }` + findstr := exec.Command(utilName, "-Command", psScript) + findstr.Env = append(os.Environ(), "PATTERN="+pattern, "FILEPATH="+location) + outputBytes, err = findstr.Output() + + } else { + utilName = "grep" + // Linux and MacOS use grep + grep := exec.Command(utilName, "-o", "-n", "-R", "-P", pattern, location) + outputBytes, err = grep.Output() + } + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { + return nil, nil + } + return nil, fmt.Errorf("could not run '%s' with provided pattern %+v", utilName, err) + } + + return outputBytes, nil +}