-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Swift support audit #231
base: dev
Are you sure you want to change the base?
Swift support audit #231
Changes from all commits
3ea86ce
606169e
fc6df17
243dfcd
a181880
08e3bd5
895ccfa
5f51f0a
4763b35
17d8b80
9db6be2
de4c5fd
26481ec
775f8e0
8c56a03
a9aebdb
93fdf5c
4cf116c
9d47eae
56d08f9
1fad622
28c54af
5ce8592
8ba3f33
34f274e
2cfe712
eab106d
cd9d4f5
c13100d
ebb37cd
da10ea2
27af044
8e2eb99
88079c2
fe71f16
b162393
fa6fce5
bd31c8a
18a3772
c3b98d0
6364688
452920e
63969c9
7604a33
5b92419
10eb136
d227dc0
76d5d9b
ac58152
4f9d0cb
cc9adc7
914366a
0e21bff
d9a3355
4bea9a1
78445f4
9f423af
1d0632f
5bcd943
76b85aa
cf6b309
a27b3de
e0a6e8d
574efca
c26837e
067148e
a95c72e
fa5db32
fca6c56
391927e
a4e0c5d
ced827e
490aa8f
4a1e634
9a7abd3
17ec960
3ed68cd
6f36dac
8878c7b
24db4c1
6165f5b
7490eb0
17548a4
97496f0
c068b15
95647b9
a88da1e
6c35cd6
4894da0
481e57b
318d469
d64a632
3aacf67
c8a84bf
bc59b78
a7a6063
004a085
ea5b311
df8559b
5f6c242
9a443ff
78cca2e
3ff3495
9d7503c
dba18a6
5693a4f
a51c90a
122768d
e599549
a85215d
dcf2d27
08faa0a
feae399
e3be3ab
30f93b2
4024ebc
dea6033
a8a45e5
f6619c5
db5e084
6e0be7d
765bd5d
985ff71
177e178
27e52d3
4dd6e96
fe8d237
15af55d
ef3e7e7
efb534b
d32b428
ebf9f43
4c1a137
493ca8e
0376803
aa946c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
package swift | ||
|
||
import ( | ||
"bufio" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/jfrog/gofrog/datastructures" | ||
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" | ||
"github.com/jfrog/jfrog-cli-security/utils" | ||
"github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" | ||
"github.com/jfrog/jfrog-cli-security/utils/techutils" | ||
"github.com/jfrog/jfrog-client-go/utils/log" | ||
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" | ||
"github.com/owenrumney/go-sarif/v2/sarif" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
const ( | ||
// VersionForMainModule - We don't have information in swift on the current package, or main module, we only have information on its | ||
// dependencies. | ||
VersionForMainModule = "0.0.0" | ||
) | ||
|
||
type Dependencies struct { | ||
Name string `json:"url,omitempty"` | ||
Version string `json:"version,omitempty"` | ||
Dependencies []*Dependencies `json:"dependencies,omitempty"` | ||
} | ||
|
||
func GetTechDependencyLocation(directDependencyName, directDependencyVersion string, descriptorPaths ...string) ([]*sarif.Location, error) { | ||
var swiftPositions []*sarif.Location | ||
for _, descriptorPath := range descriptorPaths { | ||
path.Clean(descriptorPath) | ||
if !strings.HasSuffix(descriptorPath, "Package.swift") { | ||
log.Logger.Warn("Cannot support other files besides Package.swift: %s", descriptorPath) | ||
continue | ||
} | ||
data, err := os.ReadFile(descriptorPath) | ||
if err != nil { | ||
continue | ||
} | ||
lines := strings.Split(string(data), "\n") | ||
var startLine, startCol int | ||
foundDependency := false | ||
var tempIndex int | ||
for i, line := range lines { | ||
foundDependency, tempIndex, startLine, startCol = parseSwiftLine(line, directDependencyName, directDependencyVersion, descriptorPath, i, tempIndex, startLine, startCol, lines, foundDependency, &swiftPositions) | ||
} | ||
} | ||
return swiftPositions, nil | ||
} | ||
|
||
func parseSwiftLine(line, directDependencyName, directDependencyVersion, descriptorPath string, i, tempIndex, startLine, startCol int, lines []string, foundDependency bool, swiftPositions *[]*sarif.Location) (bool, int, int, int) { | ||
if strings.Contains(line, directDependencyName) { | ||
startLine = i | ||
startCol = strings.Index(line, directDependencyName) | ||
foundDependency = true | ||
tempIndex = i | ||
} | ||
// This means we are in a new dependency (we cannot find dependency name and version together) | ||
if i > tempIndex && foundDependency && strings.Contains(line, ".package") { | ||
foundDependency = false | ||
} else if foundDependency && strings.Contains(line, directDependencyVersion) { | ||
endLine := i | ||
endCol := strings.Index(line, directDependencyVersion) + len(directDependencyVersion) + 1 | ||
var snippet string | ||
// if the tech dependency is a one-liner | ||
if endLine == startLine { | ||
snippet = lines[startLine][startCol:endCol] | ||
// else it is more than one line, so we need to parse all lines | ||
} else { | ||
for snippetLine := 0; snippetLine < endLine-startLine+1; snippetLine++ { | ||
switch snippetLine { | ||
case 0: | ||
snippet += "\n" + lines[snippetLine][startLine:] | ||
case endLine - startLine: | ||
snippet += "\n" + lines[snippetLine][:endCol] | ||
default: | ||
snippet += "\n" + lines[snippetLine] | ||
} | ||
} | ||
} | ||
*swiftPositions = append(*swiftPositions, sarifutils.CreateLocation(descriptorPath, startLine, endLine, startCol, endCol, snippet)) | ||
foundDependency = false | ||
} | ||
return foundDependency, tempIndex, startLine, startCol | ||
} | ||
|
||
func FixTechDependency(dependencyName, dependencyVersion, fixVersion string, descriptorPaths ...string) error { | ||
for _, descriptorPath := range descriptorPaths { | ||
path.Clean(descriptorPath) | ||
if !strings.HasSuffix(descriptorPath, "Package.swift") { | ||
log.Logger.Warn("Cannot support other files besides Package.swift: %s", descriptorPath) | ||
continue | ||
} | ||
data, err := os.ReadFile(descriptorPath) | ||
var newLines []string | ||
if err != nil { | ||
continue | ||
} | ||
lines := strings.Split(string(data), "\n") | ||
foundDependency := false | ||
var tempIndex int | ||
for index, line := range lines { | ||
if strings.Contains(line, dependencyName) { | ||
foundDependency = true | ||
tempIndex = index | ||
} | ||
// This means we are in a new dependency (we cannot find dependency name and version together) | ||
//nolint:gocritic | ||
if index > tempIndex && foundDependency && strings.Contains(line, ".package") { | ||
foundDependency = false | ||
} else if foundDependency && strings.Contains(line, dependencyVersion) { | ||
newLine := strings.Replace(line, dependencyVersion, fixVersion, 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace is enough to make the actual fix? should we install this so it will be changed in the "lock" file as well? (if exists one for swift) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to perform install. I think it is very invasive, I would prefer to let the customer know I changed the dependency and if he wants it to be committed he should install himself (maybe he wants to use flags?). Should I log this info? |
||
newLines = append(newLines, newLine) | ||
foundDependency = false | ||
} else { | ||
newLines = append(newLines, line) | ||
} | ||
} | ||
output := strings.Join(newLines, "\n") | ||
err = os.WriteFile(descriptorPath, []byte(output), 0644) | ||
if err != nil { | ||
return fmt.Errorf("failed to write file: %v", err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func extractNameFromSwiftGitRepo(name string) string { | ||
name = strings.TrimSuffix(name, ".git") | ||
name = strings.TrimPrefix(name, "https://") | ||
return name | ||
} | ||
|
||
func GetSwiftDependenciesGraph(data *Dependencies, dependencyMap map[string][]string, versionMap map[string]string) { | ||
data.Name = extractNameFromSwiftGitRepo(data.Name) | ||
_, ok := dependencyMap[data.Name] | ||
if !ok { | ||
dependencyMap[data.Name] = []string{} | ||
versionMap[data.Name] = data.Version | ||
} | ||
for _, dependency := range data.Dependencies { | ||
dependency.Name = extractNameFromSwiftGitRepo(dependency.Name) | ||
dependencyMap[data.Name] = append(dependencyMap[data.Name], dependency.Name) | ||
GetSwiftDependenciesGraph(dependency, dependencyMap, versionMap) | ||
} | ||
} | ||
|
||
func GetDependenciesData(exePath, currentDir string) (*Dependencies, error) { | ||
result, err := runSwiftCmd(exePath, currentDir, []string{"package", "show-dependencies", "--format", "json"}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var data *Dependencies | ||
err = json.Unmarshal(result, &data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return data, nil | ||
} | ||
|
||
func GetMainPackageName(currentDir string) (string, error) { | ||
file, err := os.Open(path.Join(currentDir, "Package.swift")) | ||
if err != nil { | ||
fmt.Println("Error opening file:", err) | ||
return "", err | ||
} | ||
defer file.Close() | ||
|
||
re := regexp.MustCompile(`name:\s*"([^"]+)"`) | ||
scanner := bufio.NewScanner(file) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
matches := re.FindStringSubmatch(line) | ||
if len(matches) > 1 { | ||
return matches[1], nil | ||
} | ||
} | ||
if err := scanner.Err(); err != nil { | ||
return "", err | ||
} | ||
return "", nil | ||
} | ||
|
||
func BuildDependencyTree(params utils.AuditParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps []string, err error) { | ||
currentDir, err := coreutils.GetWorkingDirectory() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
packageName, err := GetMainPackageName(currentDir) | ||
if err != nil { | ||
log.Warn("Failed to get package name from Package.swift file") | ||
packageName = filepath.Base(currentDir) | ||
} | ||
|
||
packageInfo := fmt.Sprintf("%s:%s", packageName, VersionForMainModule) | ||
version, exePath, err := getSwiftVersionAndExecPath() | ||
if err != nil { | ||
err = fmt.Errorf("failed while retrieving swift path: %s", err.Error()) | ||
return | ||
} | ||
log.Debug("Swift version: %s", version.GetVersion()) | ||
// Calculate pod dependencies | ||
data, err := GetDependenciesData(exePath, currentDir) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
uniqueDepsSet := datastructures.MakeSet[string]() | ||
dependencyMap := make(map[string][]string) | ||
versionMap := make(map[string]string) | ||
data.Name = packageName | ||
data.Version = VersionForMainModule | ||
GetSwiftDependenciesGraph(data, dependencyMap, versionMap) | ||
for key := range dependencyMap { | ||
if key != packageName { | ||
dependencyMap[packageName] = append(dependencyMap[packageName], key) | ||
} | ||
} | ||
versionMap[packageName] = VersionForMainModule | ||
rootNode := &xrayUtils.GraphNode{ | ||
Id: techutils.Swift.GetPackageTypeId() + packageInfo, | ||
Nodes: []*xrayUtils.GraphNode{}, | ||
} | ||
// Parse the dependencies into Xray dependency tree format | ||
parseSwiftDependenciesList(rootNode, dependencyMap, versionMap, uniqueDepsSet) | ||
dependencyTree = []*xrayUtils.GraphNode{rootNode} | ||
uniqueDeps = uniqueDepsSet.ToSlice() | ||
return | ||
} | ||
|
||
// Parse the dependencies into a Xray dependency tree format | ||
func parseSwiftDependenciesList(currNode *xrayUtils.GraphNode, dependenciesGraph map[string][]string, versionMap map[string]string, uniqueDepsSet *datastructures.Set[string]) { | ||
if currNode.NodeHasLoop() { | ||
return | ||
} | ||
uniqueDepsSet.Add(currNode.Id) | ||
pkgName := strings.Split(strings.TrimPrefix(currNode.Id, techutils.Swift.GetPackageTypeId()), ":")[0] | ||
currDepChildren := dependenciesGraph[pkgName] | ||
for _, childName := range currDepChildren { | ||
fullChildName := fmt.Sprintf("%s:%s", childName, versionMap[childName]) | ||
childNode := &xrayUtils.GraphNode{ | ||
Comment on lines
+245
to
+246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there an option that childName does not exists in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not possible because i always put something in versionMap for every child |
||
Id: techutils.Swift.GetPackageTypeId() + fullChildName, | ||
Nodes: []*xrayUtils.GraphNode{}, | ||
Parent: currNode, | ||
} | ||
currNode.Nodes = append(currNode.Nodes, childNode) | ||
parseSwiftDependenciesList(childNode, dependenciesGraph, versionMap, uniqueDepsSet) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe using
regex
will make it easierThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i prefer not using regex if possible, and this was not really hard to implement using normal string functions