1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

feat(fortifyExecuteScan): SARIF generation improvements (#3769)

* feat(fpr_to_sarif & GHAS): adjustments to fit some rules

* feat(fortifyExecuteScan): fit GH ingestion rules better

* feat(fortifyExecuteScan): readability in SARIF report

* feat(fortifyExecuteScan): restore escaped chars in XML text

* feat(fortifyExecuteScan): properly replace threadflowlocations in each threadflow

* fix(fortifyExecuteScan): fixed missing threadflow in SARIF generation

* feat(fortifyExecuteScan): properly handle threadflows when a node has another node as Reason (node-in-node edge case)

* feat(fortifyExecuteScan): better sarif ruleID field

Co-authored-by: thtri <trinhthanhhai@gmail.com>
This commit is contained in:
xgoffin 2022-05-11 17:05:51 +02:00 committed by GitHub
parent 3fea61e8b0
commit 7d9f018529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 383 additions and 248 deletions

View File

@ -22,15 +22,16 @@ type Runs struct {
// Results these structs are relevant to the Results object
type Results struct {
RuleID string `json:"ruleId"`
RuleIndex int `json:"ruleIndex"`
Level string `json:"level,omitempty"`
Message *Message `json:"message,omitempty"`
AnalysisTarget *ArtifactLocation `json:"analysisTarget,omitempty"`
Locations []Location `json:"locations,omitempty"`
CodeFlows []CodeFlow `json:"codeFlows,omitempty"`
RelatedLocations []RelatedLocation `json:"relatedLocations,omitempty"`
Properties *SarifProperties `json:"properties"`
RuleID string `json:"ruleId"`
RuleIndex int `json:"ruleIndex"`
Level string `json:"level,omitempty"`
Message *Message `json:"message,omitempty"`
AnalysisTarget *ArtifactLocation `json:"analysisTarget,omitempty"`
Locations []Location `json:"locations,omitempty"`
CodeFlows []CodeFlow `json:"codeFlows,omitempty"`
RelatedLocations []RelatedLocation `json:"relatedLocations,omitempty"`
PartialFingerprints PartialFingerprints `json:"partialFingerprints"`
Properties *SarifProperties `json:"properties"`
}
// Message to detail the finding
@ -54,8 +55,9 @@ type PhysicalLocation struct {
// ArtifactLocation describing the path of the artifact
type ArtifactLocation struct {
URI string `json:"uri"`
Index int `json:"index"`
URI string `json:"uri"`
URIBaseId string `json:"uriBaseId"`
Index int `json:"index"`
}
// Region where the finding was detected
@ -74,6 +76,13 @@ type LogicalLocation struct {
FullyQualifiedName string `json:"fullyQualifiedName"`
}
// PartialFingerprints
type PartialFingerprints struct {
FortifyInstanceID string `json:"fortifyInstanceID,omitempty"`
CheckmarxSimilarityID string `json:"checkmarxSimilarityID,omitempty"`
PrimaryLocationLineHash string `json:"primaryLocationLineHash,omitempty"`
}
// SarifProperties adding additional information/context to the finding
type SarifProperties struct {
InstanceID string `json:"instanceID,omitempty"`
@ -167,7 +176,7 @@ type RelatedPhysicalLocation struct {
// RelatedRegion
type RelatedRegion struct {
StartLine int `json:"startLine"`
StartLine int `json:"startLine,omitempty"`
StartColumn int `json:"startColumn,omitempty"`
}

View File

@ -211,6 +211,7 @@ type Node struct {
XMLName xml.Name `xml:"Node"`
IsDefault string `xml:"isDefault,attr,omitempty"`
NodeLabel string `xml:"label,attr,omitempty"`
ID int `xml:"id,attr,omitempty"`
SourceLocation SourceLocation `xml:"SourceLocation"`
Action Action `xml:"Action,omitempty"`
Reason Reason `xml:"Reason,omitempty"`
@ -493,16 +494,6 @@ type Attribute struct {
Value string `xml:"value"`
}
// Utils
func (n Node) isEmpty() bool {
return n.IsDefault == ""
}
func (a Action) isEmpty() bool {
return a.ActionData == ""
}
// ConvertFprToSarif converts the FPR file contents into SARIF format
func ConvertFprToSarif(sys System, project *models.Project, projectVersion *models.ProjectVersion, resultFilePath string, filterSet *models.FilterSet) (format.SARIF, error) {
log.Entry().Debug("Extracting FPR.")
@ -576,16 +567,29 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
log.Entry().Debug("[SARIF] Now handling results.")
for i := 0; i < len(fvdl.Vulnerabilities.Vulnerability); i++ {
result := *new(format.Results)
result.RuleID = fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.ClassID
//result.RuleID = fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.ClassID
// Handle ruleID the same way than in Rule
idArray := []string{}
if fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Kingdom != "" {
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Kingdom)
}
if fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Type != "" {
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Type)
}
if fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Subtype != "" {
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Subtype)
}
result.RuleID = "fortify-" + strings.Join(idArray, "/")
// end handle result
result.Level = "none" //TODO
//get message
for j := 0; j < len(fvdl.Description); j++ {
if fvdl.Description[j].ClassID == result.RuleID {
result.RuleIndex = j //Seems very abstract
rawMessage := fvdl.Description[j].Abstract.Text
if fvdl.Description[j].ClassID == fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.ClassID {
result.RuleIndex = j
rawMessage := unescapeXML(fvdl.Description[j].Abstract.Text)
// Replacement defintions in message
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.ReplacementDefinitions.Def); l++ {
rawMessage = strings.ReplaceAll(rawMessage, "Replace key=\""+fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"", fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
rawMessage = strings.ReplaceAll(rawMessage, "<Replace key=\""+fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"/>", fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
}
msg := new(format.Message)
msg.Text = rawMessage
@ -596,7 +600,6 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
// Handle all locations items
location := *new(format.Location)
var startingColumn int
//get location
for k := 0; k < len(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace); k++ { // k iterates on traces
//In each trace/primary, there can be one or more entries
@ -605,100 +608,142 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
threadFlow := *new(format.ThreadFlow)
//We now iterate on Entries in the trace/primary
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry); l++ { // l iterates on entries
threadFlowLocation := *new(format.Locations) //One is created regardless
//the default node dictates the interesting threadflow (location, and so on)
tfla := *new([]format.Locations) //threadflowlocationarray. Useful for the node-in-node edge case
threadFlowLocation := *new(format.Locations) //One is created regardless of the path taken afterwards
//this will populate both threadFlowLocation AND the parent location object (result.Locations[0])
if !fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.isEmpty() && fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.IsDefault == "true" {
//initalize threadFlowLocation.Location
threadFlowLocation.Location = new(format.Location)
// We check if a noderef is present: if no (index of ref is the default 0), this is a "real" node. As a measure of safety (in case a node refers to nodeid 0), we add another check: the node must have a label or a isdefault value
if fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].NodeRef.RefId == 0 && (fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.NodeLabel != "" || fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.IsDefault != "") {
//initalize the current location object, it will be added to threadFlowLocation.Location
tfloc := new(format.Location)
//get artifact location
for j := 0; j < len(fvdl.Build.SourceFiles); j++ { // j iterates on source files
if fvdl.Build.SourceFiles[j].Name == fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.Path {
threadFlowLocation.Location.PhysicalLocation.ArtifactLocation.Index = j
tfloc.PhysicalLocation.ArtifactLocation.Index = j + 1
tfloc.PhysicalLocation.ArtifactLocation.URI = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.Path
tfloc.PhysicalLocation.ArtifactLocation.URIBaseId = "%SRCROOT%"
break
}
}
//get region & context region
threadFlowLocation.Location.PhysicalLocation.Region.StartLine = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.Line
tfloc.PhysicalLocation.Region.StartLine = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.Line
tfloc.PhysicalLocation.Region.EndLine = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.LineEnd
tfloc.PhysicalLocation.Region.StartColumn = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.ColStart
tfloc.PhysicalLocation.Region.EndColumn = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.ColEnd
//Snippet is handled last
//threadFlowLocation.Location.PhysicalLocation.Region.Snippet.Text = "foobar"
targetSnippetId := fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.Snippet
for j := 0; j < len(fvdl.Snippets); j++ {
if fvdl.Snippets[j].SnippetId == targetSnippetId {
threadFlowLocation.Location.PhysicalLocation.ContextRegion.StartLine = fvdl.Snippets[j].StartLine
threadFlowLocation.Location.PhysicalLocation.ContextRegion.EndLine = fvdl.Snippets[j].EndLine
tfloc.PhysicalLocation.ContextRegion.StartLine = fvdl.Snippets[j].StartLine
tfloc.PhysicalLocation.ContextRegion.EndLine = fvdl.Snippets[j].EndLine
snippetSarif := new(format.SnippetSarif)
snippetSarif.Text = fvdl.Snippets[j].Text
threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet = snippetSarif
tfloc.PhysicalLocation.ContextRegion.Snippet = snippetSarif
break
}
}
//parse SourceLocation object for the startColumn value, store it appropriately
startingColumn = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.SourceLocation.ColStart
//check for existance of action object, and if yes, save message
if !fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.isEmpty() {
threadFlowLocation.Location.Message = new(format.Message)
threadFlowLocation.Location.Message.Text = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData
// Handle snippet
snippetTarget := ""
switch fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.Type {
case "Assign":
snippetWords := strings.Split(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData, " ")
if snippetWords[0] == "Assignment" {
snippetTarget = snippetWords[2]
} else {
snippetTarget = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData
// if a label is passed, put it as message
if fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.NodeLabel != "" {
tfloc.Message = new(format.Message)
tfloc.Message.Text = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.NodeLabel
} else {
// otherwise check for existance of action object, and if yes, save message
if !(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData == "") {
tfloc.Message = new(format.Message)
tfloc.Message.Text = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData
// Handle snippet
snippetTarget := handleSnippet(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.Type, fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData)
if tfloc.PhysicalLocation.ContextRegion.Snippet != nil {
physLocationSnippetLines := strings.Split(tfloc.PhysicalLocation.ContextRegion.Snippet.Text, "\n")
snippetText := ""
for j := 0; j < len(physLocationSnippetLines); j++ {
if strings.Contains(physLocationSnippetLines[j], snippetTarget) {
snippetText = physLocationSnippetLines[j]
break
}
}
snippetSarif := new(format.SnippetSarif)
if snippetText != "" {
snippetSarif.Text = snippetText
} else {
snippetSarif.Text = tfloc.PhysicalLocation.ContextRegion.Snippet.Text
}
tfloc.PhysicalLocation.Region.Snippet = snippetSarif
}
case "InCall":
snippetTarget = strings.Split(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData, "(")[0]
case "OutCall":
snippetTarget = strings.Split(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData, "(")[0]
case "InOutCall":
snippetTarget = strings.Split(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData, "(")[0]
case "Return":
snippetTarget = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData
case "Read":
snippetWords := strings.Split(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData, " ")
if len(snippetWords) > 1 {
snippetTarget = " " + snippetWords[1]
} else {
snippetTarget = snippetWords[0]
} else {
if tfloc.PhysicalLocation.ContextRegion.Snippet != nil {
snippetSarif := new(format.SnippetSarif)
snippetSarif.Text = tfloc.PhysicalLocation.ContextRegion.Snippet.Text
tfloc.PhysicalLocation.Region.Snippet = snippetSarif
}
default:
snippetTarget = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Action.ActionData
}
if threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet != nil {
physLocationSnippetLines := strings.Split(threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet.Text, "\n")
snippetText := ""
for j := 0; j < len(physLocationSnippetLines); j++ {
if strings.Contains(physLocationSnippetLines[j], snippetTarget) {
snippetText = physLocationSnippetLines[j]
}
location = *tfloc
//set Kinds
threadFlowLocation.Location = tfloc
threadFlowLocation.Kinds = append(threadFlowLocation.Kinds, "review") //TODO
threadFlowLocation.Index = 0 // to be safe?
tfla = append(tfla, threadFlowLocation)
// "Node-in-node" edge case! in some cases the "Reason" object will contain a "Trace>Primary>Entry>Node" object
// Check for it at depth 1 only, as an in-case
if len(fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry) > 0 {
ninThreadFlowLocation := *new(format.Locations)
if fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].NodeRef.RefId != 0 {
// As usual, only the index for a ref
ninThreadFlowLocation.Index = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].NodeRef.RefId + 1
} else {
// Build a new "node-in-node" tfloc, it will be appended to tfla
nintfloc := new(format.Location)
// artifactlocation
for j := 0; j < len(fvdl.Build.SourceFiles); j++ {
if fvdl.Build.SourceFiles[j].Name == fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.Path {
nintfloc.PhysicalLocation.ArtifactLocation.Index = j + 1
nintfloc.PhysicalLocation.ArtifactLocation.URI = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.Path
nintfloc.PhysicalLocation.ArtifactLocation.URIBaseId = "%SRCROOT%"
break
}
}
snippetSarif := new(format.SnippetSarif)
if snippetText != "" {
snippetSarif.Text = snippetText
} else {
snippetSarif.Text = threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet.Text
// region & context region
nintfloc.PhysicalLocation.Region.StartLine = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.Line
nintfloc.PhysicalLocation.Region.EndLine = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.LineEnd
nintfloc.PhysicalLocation.Region.StartColumn = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.ColStart
nintfloc.PhysicalLocation.Region.EndColumn = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.ColEnd
// snippet
targetSnippetId := fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.SourceLocation.Snippet
for j := 0; j < len(fvdl.Snippets); j++ {
if fvdl.Snippets[j].SnippetId == targetSnippetId {
nintfloc.PhysicalLocation.ContextRegion.StartLine = fvdl.Snippets[j].StartLine
nintfloc.PhysicalLocation.ContextRegion.EndLine = fvdl.Snippets[j].EndLine
snippetSarif := new(format.SnippetSarif)
snippetSarif.Text = fvdl.Snippets[j].Text
nintfloc.PhysicalLocation.ContextRegion.Snippet = snippetSarif
break
}
}
threadFlowLocation.Location.PhysicalLocation.Region.Snippet = snippetSarif
}
} else {
if threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet != nil {
snippetSarif := new(format.SnippetSarif)
snippetSarif.Text = threadFlowLocation.Location.PhysicalLocation.ContextRegion.Snippet.Text
threadFlowLocation.Location.PhysicalLocation.Region.Snippet = snippetSarif
// label as message
if fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.NodeLabel != "" {
nintfloc.Message = new(format.Message)
nintfloc.Message.Text = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].Node.Reason.Trace.Primary.Entry[0].Node.NodeLabel
}
ninThreadFlowLocation.Location = nintfloc
ninThreadFlowLocation.Index = 0 // Safety
}
tfla = append(tfla, ninThreadFlowLocation)
}
location = *threadFlowLocation.Location
//set Kinds
threadFlowLocation.Kinds = append(threadFlowLocation.Kinds, "unknown") //TODO
// END edge case
} else { //is not a main threadflow: just register NodeRef index in threadFlowLocation
threadFlowLocation.Index = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].NodeRef.RefId
// Sarif does not provision 0 as a valid array index, so we increment the node ref id
// Each index i serves to reference the i-th object in run.threadFlowLocations
threadFlowLocation.Index = fvdl.Vulnerabilities.Vulnerability[i].AnalysisInfo.Trace[k].Primary.Entry[l].NodeRef.RefId + 1
tfla = append(tfla, threadFlowLocation)
}
//add the threadflowlocation to the list of locations
threadFlow.Locations = append(threadFlow.Locations, threadFlowLocation)
threadFlow.Locations = append(threadFlow.Locations, tfla...)
}
codeFlow.ThreadFlows = append(codeFlow.ThreadFlows, threadFlow)
result.CodeFlows = append(result.CodeFlows, codeFlow)
@ -716,23 +761,20 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
relatedLocation.PhysicalLocation.ArtifactLocation = location.PhysicalLocation.ArtifactLocation
relatedLocation.PhysicalLocation.Region = *new(format.RelatedRegion)
relatedLocation.PhysicalLocation.Region.StartLine = location.PhysicalLocation.Region.StartLine
relatedLocation.PhysicalLocation.Region.StartColumn = startingColumn
relatedLocation.PhysicalLocation.Region.StartColumn = location.PhysicalLocation.Region.StartColumn
result.RelatedLocations = append(result.RelatedLocations, relatedLocation)
//handle partialFingerprints
result.PartialFingerprints.FortifyInstanceID = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID
result.PartialFingerprints.PrimaryLocationLineHash = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID //Fixit
//handle properties
prop := new(format.SarifProperties)
prop.InstanceSeverity = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity
prop.Confidence = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.Confidence
prop.InstanceID = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID
//Use a query to get the audit data
// B5C0FEFD-CCB2-4F21-A9D7-87AE600A5885 is "custom rules": handle differently?
if result.RuleID == "B5C0FEFD-CCB2-4F21-A9D7-87AE600A5885" {
// Custom Rules has no audit value: it's notificaiton in the FVDL only.
prop.Audited = true
prop.ToolAuditMessage = "Custom Rules: not a vuln"
prop.ToolState = "Not an Issue"
prop.ToolStateIndex = 1
} else if sys != nil {
//Get the audit data
if sys != nil {
if err := integrateAuditData(prop, fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID, sys, project, projectVersion, auditData, filterSet, oneRequestPerIssueMode); err != nil {
log.Entry().Debug(err)
prop.Audited = false
@ -765,121 +807,141 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
for j := 0; j < len(fvdl.Vulnerabilities.Vulnerability); j++ { //j iterates on vulns to find the name
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.ClassID == fvdl.EngineData.RuleInfo[i].RuleID {
var nameArray []string
var idArray []string
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom != "" {
nameArray = append(nameArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom)
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom)
words := strings.Split(fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom, " ")
for index, element := range words { // These are required to ensure that titlecase is respected in titles, part of sarif "friendly name" rules
words[index] = strings.Title(strings.ToLower(element))
}
nameArray = append(nameArray, words...)
}
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type != "" {
nameArray = append(nameArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type)
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type)
words := strings.Split(fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type, " ")
for index, element := range words {
words[index] = strings.Title(strings.ToLower(element))
}
nameArray = append(nameArray, words...)
}
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype != "" {
nameArray = append(nameArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype)
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype)
words := strings.Split(fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype, " ")
for index, element := range words {
words[index] = strings.Title(strings.ToLower(element))
}
nameArray = append(nameArray, words...)
}
sarifRule.Name = strings.Join(nameArray, "/")
sarifRule.ID = "fortify-" + strings.Join(idArray, "/")
sarifRule.Name = strings.Join(nameArray, "")
defaultConfig := new(format.DefaultConfiguration)
defaultConfig.Level = "warning" // Default value
defaultConfig.Properties.DefaultSeverity = fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.DefaultSeverity
sarifRule.DefaultConfiguration = defaultConfig
break
}
}
//Descriptions
for j := 0; j < len(fvdl.Description); j++ {
if fvdl.Description[j].ClassID == sarifRule.ID {
rawAbstract := fvdl.Description[j].Abstract.Text
rawExplanation := fvdl.Description[j].Explanation.Text
// Replacement defintions in abstract/explanation
for k := 0; k < len(fvdl.Vulnerabilities.Vulnerability); k++ { // Iterate on vulns to find the correct one (where ReplacementDefinitions are)
if fvdl.Vulnerabilities.Vulnerability[k].ClassInfo.ClassID == fvdl.Description[j].ClassID {
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def); l++ {
rawAbstract = strings.ReplaceAll(rawAbstract, "Replace key=\""+fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"", fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
rawExplanation = strings.ReplaceAll(rawExplanation, "Replace key=\""+fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"", fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
//Descriptions
for j := 0; j < len(fvdl.Description); j++ {
if fvdl.Description[j].ClassID == sarifRule.GUID {
rawAbstract := unescapeXML(fvdl.Description[j].Abstract.Text)
rawExplanation := unescapeXML(fvdl.Description[j].Explanation.Text)
// Replacement defintions in abstract/explanation
for k := 0; k < len(fvdl.Vulnerabilities.Vulnerability); k++ { // Iterate on vulns to find the correct one (where ReplacementDefinitions are)
if fvdl.Vulnerabilities.Vulnerability[k].ClassInfo.ClassID == fvdl.Description[j].ClassID {
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def); l++ {
rawAbstract = strings.ReplaceAll(rawAbstract, "<Replace key=\""+fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"/>", fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
rawExplanation = strings.ReplaceAll(rawExplanation, "<Replace key=\""+fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefKey+"\"/>", fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.Def[l].DefValue)
}
// Replacement locationdef in explanation
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef); l++ {
rawExplanation = strings.ReplaceAll(rawExplanation, fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef[l].Key, fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef[l].Path)
}
// If Description has a CustomDescription, add it for good measure
if fvdl.Description[j].CustomDescription.RuleID != "" {
rawExplanation = rawExplanation + "\n;" + fvdl.Description[j].CustomDescription.Explanation.Text
}
sd := new(format.Message)
sd.Text = rawAbstract
sarifRule.ShortDescription = sd
fd := new(format.Message)
fd.Text = rawExplanation
sarifRule.FullDescription = fd
break
}
}
// Replacement locationdef in explanation
for l := 0; l < len(fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef); l++ {
rawExplanation = strings.ReplaceAll(rawExplanation, fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef[l].Key, fvdl.Vulnerabilities.Vulnerability[k].AnalysisInfo.ReplacementDefinitions.LocationDef[l].Path)
}
// If Description has a CustomDescription, add it for good measure
if fvdl.Description[j].CustomDescription.RuleID != "" {
rawExplanation = rawExplanation + "\n;" + fvdl.Description[j].CustomDescription.Explanation.Text
}
sd := new(format.Message)
sd.Text = rawAbstract
sarifRule.ShortDescription = sd
fd := new(format.Message)
fd.Text = rawExplanation
sarifRule.FullDescription = fd
break
}
}
//properties
//Prepare a CWE id object as an in-case
cweIds := []string{}
//scan for the properties we want:
var propArray [][]string
for j := 0; j < len(fvdl.EngineData.RuleInfo[i].MetaInfoGroup); j++ {
if (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Accuracy") || (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Impact") || (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Probability") {
propArray = append(propArray, []string{fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name, fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Data})
} else if fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "altcategoryCWE" {
//Get all CWE IDs. First, split on ", "
rawCweIds := strings.Split(fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Data, ", ")
//If not "None", split each string on " " and add its 2nd index
if rawCweIds[0] != "None" {
for k := 0; k < len(rawCweIds); k++ {
cweId := strings.Split(rawCweIds[k], " ")[2]
//Fill the cweIdsForTaxonomies map if not already in
if _, isIn := cweIdsForTaxonomies[cweId]; !isIn {
cweIdsForTaxonomies[cweId] = cweId
}
cweIds = append(cweIds, cweId)
}
} else {
cweIds = append(cweIds, rawCweIds[0])
}
}
}
var ruleProp *format.SarifRuleProperties
if len(propArray) != 0 {
ruleProp = new(format.SarifRuleProperties)
for j := 0; j < len(propArray); j++ {
if propArray[j][0] == "Accuracy" {
ruleProp.Accuracy = propArray[j][1]
} else if propArray[j][0] == "Impact" {
ruleProp.Impact = propArray[j][1]
} else if propArray[j][0] == "Probability" {
ruleProp.Probability = propArray[j][1]
}
}
}
sarifRule.Properties = ruleProp
//relationships: will most likely require some expansion
//One relationship per CWE id
for j := 0; j < len(cweIds); j++ {
sarifRule.Properties.Tags = append(sarifRule.Properties.Tags, "external/cwe/cwe-"+cweIds[j])
rls := *new(format.Relationships)
rls.Target.Id = cweIds[j]
rls.Target.ToolComponent.Name = "CWE"
rls.Target.ToolComponent.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
rls.Kinds = append(rls.Kinds, "relevant")
sarifRule.Relationships = append(sarifRule.Relationships, rls)
}
// Add a helpURI as some processors require it
sarifRule.HelpURI = "https://vulncat.fortify.com/en/weakness"
//Finalize: append the rule
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
// A rule vuln has been found for this rule, no need to keep iterating
break
}
}
// Avoid empty descriptions to respect standard
//if sarifRule.ShortDescription.Text == "" {
// sarifRule.ShortDescription.Text = "None."
//}
//if sarifRule.FullDescription.Text == "" { // OR USE OMITEMPTY
// sarifRule.FullDescription.Text = "None."
//}
//properties
//Prepare a CWE id object as an in-case
cweIds := []string{}
//scan for the properties we want:
var propArray [][]string
for j := 0; j < len(fvdl.EngineData.RuleInfo[i].MetaInfoGroup); j++ {
if (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Accuracy") || (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Impact") || (fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "Probability") {
propArray = append(propArray, []string{fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name, fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Data})
} else if fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Name == "altcategoryCWE" {
//Get all CWE IDs. First, split on ", "
rawCweIds := strings.Split(fvdl.EngineData.RuleInfo[i].MetaInfoGroup[j].Data, ", ")
//If not "None", split each string on " " and add its 2nd index
if rawCweIds[0] != "None" {
for k := 0; k < len(rawCweIds); k++ {
cweId := strings.Split(rawCweIds[k], " ")[2]
//Fill the cweIdsForTaxonomies map if not already in
if _, isIn := cweIdsForTaxonomies[cweId]; !isIn {
cweIdsForTaxonomies[cweId] = cweId
}
cweIds = append(cweIds, cweId)
}
} else {
cweIds = append(cweIds, rawCweIds[0])
}
}
}
var ruleProp *format.SarifRuleProperties
if len(propArray) != 0 {
ruleProp = new(format.SarifRuleProperties)
for j := 0; j < len(propArray); j++ {
if propArray[j][0] == "Accuracy" {
ruleProp.Accuracy = propArray[j][1]
} else if propArray[j][0] == "Impact" {
ruleProp.Impact = propArray[j][1]
} else if propArray[j][0] == "Probability" {
ruleProp.Probability = propArray[j][1]
}
}
}
sarifRule.Properties = ruleProp
//relationships: will most likely require some expansion
//One relationship per CWE id
for j := 0; j < len(cweIds); j++ {
rls := *new(format.Relationships)
rls.Target.Id = cweIds[j]
rls.Target.ToolComponent.Name = "CWE"
rls.Target.ToolComponent.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
rls.Kinds = append(rls.Kinds, "relevant")
sarifRule.Relationships = append(sarifRule.Relationships, rls)
}
//Finalize: append the rule
tool.Driver.Rules = append(tool.Driver.Rules, sarifRule)
}
//supportedTaxonomies
sTax := *new(format.SupportedTaxonomies) //This object seems fixed, but it will have to be checked
sTax.Name = "CWE"
sTax.Index = 0
sTax.Index = 1
sTax.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
tool.Driver.SupportedTaxonomies = append(tool.Driver.SupportedTaxonomies, sTax)
@ -917,7 +979,12 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
//handle originalUriBaseIds
oubi := new(format.OriginalUriBaseIds)
oubi.SrcRoot.Uri = "file:///" + fvdl.Build.SourceBasePath + "/"
prefix := "file://"
if fvdl.Build.SourceBasePath[0] == '/' {
oubi.SrcRoot.Uri = prefix + fvdl.Build.SourceBasePath + "/"
} else {
oubi.SrcRoot.Uri = prefix + "/" + fvdl.Build.SourceBasePath + "/"
}
sarif.Runs[0].OriginalUriBaseIds = oubi
//handle artifacts
@ -945,31 +1012,25 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
//handle threadFlowLocations
log.Entry().Debug("[SARIF] Now handling threadFlowLocations.")
threadFlowLocationsObject := []format.Locations{}
//prepare a check object
//to ensure an exact replacement in case a threadFlowLocation object refers to another, we prepare a map
threadFlowIndexMap := make(map[int]([]int)) // This will store indexes, we will work with it only to reduce item copies to a minimum
for i := 0; i < len(fvdl.UnifiedNodePool.Node); i++ {
unique := true
//Uniqueness Check
for check := 0; check < i; check++ {
if fvdl.UnifiedNodePool.Node[i].SourceLocation.Snippet == fvdl.UnifiedNodePool.Node[check].SourceLocation.Snippet &&
fvdl.UnifiedNodePool.Node[i].Action.ActionData == fvdl.UnifiedNodePool.Node[check].Action.ActionData {
unique = false
}
}
if !unique {
continue
}
locations := *new(format.Locations)
threadFlowIndexMap[i+1] = append(threadFlowIndexMap[i+1], i+1)
loc := new(format.Location)
//get artifact location
for j := 0; j < len(fvdl.Build.SourceFiles); j++ { // j iterates on source files
if fvdl.Build.SourceFiles[j].Name == fvdl.UnifiedNodePool.Node[i].SourceLocation.Path {
loc.PhysicalLocation.ArtifactLocation.Index = j
loc.PhysicalLocation.ArtifactLocation.Index = j + 1
loc.PhysicalLocation.ArtifactLocation.URI = fvdl.UnifiedNodePool.Node[i].SourceLocation.Path
loc.PhysicalLocation.ArtifactLocation.URIBaseId = "%SRCROOT%"
break
}
}
//get region & context region
loc.PhysicalLocation.Region.StartLine = fvdl.UnifiedNodePool.Node[i].SourceLocation.Line
//loc.PhysicalLocation.Region.Snippet.Text = "foobar" //TODO
loc.PhysicalLocation.Region.EndLine = fvdl.UnifiedNodePool.Node[i].SourceLocation.LineEnd
loc.PhysicalLocation.Region.StartColumn = fvdl.UnifiedNodePool.Node[i].SourceLocation.ColStart
loc.PhysicalLocation.Region.EndColumn = fvdl.UnifiedNodePool.Node[i].SourceLocation.ColEnd
targetSnippetId := fvdl.UnifiedNodePool.Node[i].SourceLocation.Snippet
for j := 0; j < len(fvdl.Snippets); j++ {
if fvdl.Snippets[j].SnippetId == targetSnippetId {
@ -983,34 +1044,10 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
}
loc.Message = new(format.Message)
loc.Message.Text = fvdl.UnifiedNodePool.Node[i].Action.ActionData
// Handle snippet
snippetTarget := ""
switch fvdl.UnifiedNodePool.Node[i].Action.Type {
case "Assign":
snippetWords := strings.Split(fvdl.UnifiedNodePool.Node[i].Action.ActionData, " ")
if snippetWords[0] == "Assignment" {
snippetTarget = snippetWords[2]
} else {
snippetTarget = fvdl.UnifiedNodePool.Node[i].Action.ActionData
}
case "InCall":
snippetTarget = strings.Split(fvdl.UnifiedNodePool.Node[i].Action.ActionData, "(")[0]
case "OutCall":
snippetTarget = strings.Split(fvdl.UnifiedNodePool.Node[i].Action.ActionData, "(")[0]
case "InOutCall":
snippetTarget = strings.Split(fvdl.UnifiedNodePool.Node[i].Action.ActionData, "(")[0]
case "Return":
snippetTarget = fvdl.UnifiedNodePool.Node[i].Action.ActionData
case "Read":
snippetWords := strings.Split(fvdl.UnifiedNodePool.Node[i].Action.ActionData, " ")
if len(snippetWords) > 1 {
snippetTarget = " " + snippetWords[1]
} else {
snippetTarget = snippetWords[0]
}
default:
snippetTarget = fvdl.UnifiedNodePool.Node[i].Action.ActionData
}
snippetTarget := handleSnippet(fvdl.UnifiedNodePool.Node[i].Action.Type, fvdl.UnifiedNodePool.Node[i].Action.ActionData)
if loc.PhysicalLocation.ContextRegion.Snippet != nil {
physLocationSnippetLines := strings.Split(loc.PhysicalLocation.ContextRegion.Snippet.Text, "\n")
snippetText := ""
@ -1028,13 +1065,40 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
}
loc.PhysicalLocation.Region.Snippet = snippetSarif
}
locations.Location = loc
locations.Kinds = append(locations.Kinds, "unknown")
threadFlowLocationsObject = append(threadFlowLocationsObject, locations)
log.Entry().Debug("Compute eventual sub-nodes")
threadFlowIndexMap[i+1] = computeLocationPath(fvdl, i+1) // Recursively traverse array
locs := format.Locations{Location: loc}
threadFlowLocationsObject = append(threadFlowLocationsObject, locs)
}
sarif.Runs[0].ThreadFlowLocations = threadFlowLocationsObject
// Now, iterate on threadflows in each result, and replace eventual indexes...
for i := 0; i < len(sarif.Runs[0].Results); i++ {
for cf := 0; cf < len(sarif.Runs[0].Results[i].CodeFlows); cf++ {
for tf := 0; tf < len(sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows); tf++ {
log.Entry().Debug("Handling tf: ", tf, "from instance ", sarif.Runs[0].Results[i].PartialFingerprints.FortifyInstanceID)
newLocations := *new([]format.Locations)
for j := 0; j < len(sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows[tf].Locations); j++ {
if sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows[tf].Locations[j].Index != 0 {
indexes := threadFlowIndexMap[sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows[tf].Locations[j].Index]
log.Entry().Debug("Indexes found: ", indexes)
for rep := 0; rep < len(indexes); rep++ {
newLocations = append(newLocations, sarif.Runs[0].ThreadFlowLocations[indexes[rep]-1])
newLocations[rep].Index = 0 // void index
}
} else {
newLocations = append(newLocations, sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows[tf].Locations[j])
}
}
sarif.Runs[0].Results[i].CodeFlows[cf].ThreadFlows[tf].Locations = newLocations
}
}
}
// Threadflowlocations is no loger useful: voiding it will make for smaller reports
sarif.Runs[0].ThreadFlowLocations = []format.Locations{}
//handle taxonomies
//Only one exists apparently: CWE. It is fixed
taxonomy := *new(format.Taxonomies)
@ -1078,8 +1142,7 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
}
}
if len(data) != 1 { //issueInstanceID is supposedly unique so len(data) = 1
//log.Entry().Error("not exactly 1 issue found, found " + fmt.Sprint(len(data)))
return errors.New("not exactly 1 issue found for instance ID " + issueInstanceID + ", found " + fmt.Sprint(len(data)))
return errors.New("not exactly 1 issue found, found " + fmt.Sprint(len(data)))
}
ruleProp.Audited = data[0].Audited
ruleProp.ToolSeverity = *data[0].Friority
@ -1119,7 +1182,7 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
if err != nil {
return err
}
ruleProp.ToolAuditMessage = *commentData[0].Comment
ruleProp.ToolAuditMessage = unescapeXML(*commentData[0].Comment)
}
if filterSet != nil {
for i := 0; i < len(filterSet.Folders); i++ {
@ -1134,3 +1197,66 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
}
return nil
}
// Factorizes some code used to obtain the relevant value for a snippet based on the type given by Fortify
func handleSnippet(snippetType string, snippet string) string {
snippetTarget := ""
switch snippetType {
case "Assign":
snippetWords := strings.Split(snippet, " ")
if snippetWords[0] == "Assignment" {
snippetTarget = snippetWords[2]
} else {
snippetTarget = snippet
}
case "InCall":
snippetTarget = strings.Split(snippet, "(")[0]
case "OutCall":
snippetTarget = strings.Split(snippet, "(")[0]
case "InOutCall":
snippetTarget = strings.Split(snippet, "(")[0]
case "Return":
snippetTarget = snippet
case "Read":
snippetWords := strings.Split(snippet, " ")
if len(snippetWords) > 1 {
snippetTarget = " " + snippetWords[1]
} else {
snippetTarget = snippetWords[0]
}
default:
snippetTarget = snippet
}
return snippetTarget
}
func unescapeXML(input string) string {
raw := input
// Post-treat string to change the XML escaping generated by Unmarshal
raw = strings.ReplaceAll(raw, "&lt;", "<")
raw = strings.ReplaceAll(raw, "&gt;", ">")
raw = strings.ReplaceAll(raw, "&amp;", "&")
raw = strings.ReplaceAll(raw, "&apos;", "'")
raw = strings.ReplaceAll(raw, "&quot;", "\"")
return raw
}
// Used to build a reference array of index for the successors of each node in the UnifiedNodePool
func computeLocationPath(fvdl FVDL, input int) []int {
log.Entry().Debug("Computing for ID ", input)
// Find the successors of input
var subnodes []int
var result []int
for j := 0; j < len(fvdl.UnifiedNodePool.Node[input-1].Reason.Trace.Primary.Entry); j++ {
if fvdl.UnifiedNodePool.Node[input-1].Reason.Trace.Primary.Entry[j].NodeRef.RefId != 0 && fvdl.UnifiedNodePool.Node[input-1].Reason.Trace.Primary.Entry[j].NodeRef.RefId != (input-1) {
subnodes = append(subnodes, fvdl.UnifiedNodePool.Node[input-1].Reason.Trace.Primary.Entry[j].NodeRef.RefId+1)
}
}
result = append(result, input)
log.Entry().Debug("Successors: ", subnodes)
for j := 0; j < len(subnodes); j++ {
result = append(result, computeLocationPath(fvdl, subnodes[j])...)
}
log.Entry().Debug("Finishing computing for ID ", input)
return result
}