package groovy
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/xmirrorsecurity/opensca-cli/v3/cmd/config"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/logs"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/model"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/filter"
"github.com/xmirrorsecurity/opensca-cli/v3/opensca/sca/java"
)
func ParseGradle(ctx context.Context, files []*model.File) []*model.DepGraph {
v := Variable{}
gradle := []*model.File{}
for _, f := range files {
if filter.GroovyGradle(f.Relpath()) {
v.Scan(f)
gradle = append(gradle, f)
}
}
var roots []*model.DepGraph
for _, f := range gradle {
_dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph {
return &model.DepGraph{
Vendor: s[0],
Name: s[1],
Version: s[2],
Develop: s[3] == "dev",
}
}).LoadOrStore
root := &model.DepGraph{Path: f.Relpath()}
f.ReadLineNoComment(model.CTypeComment, func(line string) {
line = v.Replace(line)
for _, re := range regexs {
match := re.FindStringSubmatch(line)
if len(match) < 4 {
continue
}
vendor := match[1]
name := match[2]
version := match[3]
index := strings.Index(version, "@")
if index != -1 {
version = version[:index]
}
if version == "" || strings.Contains(version, "$") {
continue
}
dev := ""
if strings.Contains(strings.ToLower(line), "testimplementation") {
dev = "dev"
}
root.AppendChild(_dep(vendor, name, version, dev))
}
})
roots = append(roots, root)
}
for i, root := range roots {
virPom := &java.Pom{File: model.NewFile(root.Path, root.Path)}
for _, dep := range root.Children {
virPom.Dependencies = append(virPom.Dependencies, &java.PomDependency{GroupId: dep.Vendor, ArtifactId: dep.Name, Version: dep.Version})
}
java.ParsePoms(ctx, []*java.Pom{virPom}, nil, func(pom *java.Pom, pomResult *model.DepGraph) {
roots[i] = pomResult
})
}
return roots
}
var regexs = []*regexp.Regexp{
regexp.MustCompile(`group: ?['"]([a-zA-Z]+[^\s"']+)['"], ?name: ?['"]([a-zA-Z]+[^\s"']+)['"], ?version: ?['"]([^\s"']+)['"]`),
regexp.MustCompile(`group: ?['"]([a-zA-Z]+[^\s"']+)['"], ?module: ?['"]([a-zA-Z]+[^\s"']+)['"], ?version: ?['"]([^\s"']+)['"]`),
regexp.MustCompile(`['"]([a-zA-Z]+[^\s:'"]+):([a-zA-Z]+[^\s:'"]+):([^\s:'"]+)['"]`),
}
var openscaGradle []byte
type gradleDep struct {
GroupId string `json:"groupId"`
ArtifactId string `json:"artifactId"`
Version string `json:"version"`
Children []*gradleDep `json:"children"`
}
func GradleTree(ctx context.Context, dir *model.File) []*model.DepGraph {
if !config.Conf().Optional.Dynamic {
return nil
}
if dir == nil {
return nil
}
if _, err := exec.LookPath("gradle"); err != nil {
return nil
}
path := filepath.Join(dir.Abspath(), "opensca.gradle")
if err := os.WriteFile(path, openscaGradle, 0777); err != nil {
logs.Warn(err)
}
defer os.Remove(path)
cmd := exec.CommandContext(ctx, "gradle", "--I", "opensca.gradle", "opensca")
cmd.Dir = dir.Abspath()
out, _ := cmd.CombinedOutput()
_dep := model.NewDepGraphMap(nil, func(s ...string) *model.DepGraph {
return &model.DepGraph{
Vendor: s[0],
Name: s[1],
Version: s[2],
}
}).LoadOrStore
var roots []*model.DepGraph
startTag := `openscaDepStart`
endTag := `openscaDepEnd`
for {
startIndex, endIndex := bytes.Index(out, []byte(startTag)), bytes.Index(out, []byte(endTag))
if startIndex < 0 || endIndex < 0 {
break
}
data := out[startIndex+len(startTag) : endIndex]
out = out[endIndex+1:]
gdep := &gradleDep{}
err := json.Unmarshal(data, &gdep.Children)
if err != nil {
logs.Warn(err)
}
root := &model.DepGraph{Vendor: gdep.GroupId, Name: gdep.ArtifactId, Version: gdep.Version, Expand: gdep, Path: dir.Relpath()}
root.ForEachNode(func(p, n *model.DepGraph) bool {
g := n.Expand.(*gradleDep)
for _, c := range g.Children {
dep := _dep(c.GroupId, c.ArtifactId, c.Version)
if dep == nil {
continue
}
if dep.Expand == nil {
dep.Expand = c
}
n.AppendChild(dep)
}
return true
})
roots = append(roots, root)
}
return roots
}