You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
feat(fortifyExecuteScan): further improvements to the SARIF generation (#3799)
* feat(fortfiyExecuteScan): proper XML unescaping, added rulepacks to SARIF, added kingdom/type/subtype to tags * feat(fortifyExecuteScan): proper handling of severity, kinds, levels in SARIF * fix(fortifyExecuteScan): edge case when handling properties taht could lead to a crash * fix(fortifyExecuteScan): ensure SARIF processing is done after latest FPR is processed by SSC
This commit is contained in:
@@ -247,7 +247,15 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
|
|||||||
return reports, fmt.Errorf(message+": %w", err)
|
return reports, fmt.Errorf(message+": %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Place conversion beforehand, or audit will stop the pipeline and conversion will not take place?
|
log.Entry().Infof("Ensuring latest FPR is processed for project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||||
|
// Ensure latest FPR is processed
|
||||||
|
err = verifyScanResultsFinishedUploading(config, sys, projectVersion.ID, buildLabel, filterSet,
|
||||||
|
10*time.Second, time.Duration(config.PollingMinutes)*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return reports, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SARIF conversion done after latest FPR is processed, but before the compliance is checked
|
||||||
if config.ConvertToSarif {
|
if config.ConvertToSarif {
|
||||||
resultFilePath := fmt.Sprintf("%vtarget/result.fpr", config.ModulePath)
|
resultFilePath := fmt.Sprintf("%vtarget/result.fpr", config.ModulePath)
|
||||||
log.Entry().Info("Calling conversion to SARIF function.")
|
log.Entry().Info("Calling conversion to SARIF function.")
|
||||||
@@ -262,13 +270,8 @@ func runFortifyScan(config fortifyExecuteScanOptions, sys fortify.System, utils
|
|||||||
}
|
}
|
||||||
reports = append(reports, paths...)
|
reports = append(reports, paths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
log.Entry().Infof("Starting audit status check on project %v with version %v and project version ID %v", fortifyProjectName, fortifyProjectVersion, projectVersion.ID)
|
||||||
// Ensure latest FPR is processed
|
|
||||||
err = verifyScanResultsFinishedUploading(config, sys, projectVersion.ID, buildLabel, filterSet,
|
|
||||||
10*time.Second, time.Duration(config.PollingMinutes)*time.Minute)
|
|
||||||
if err != nil {
|
|
||||||
return reports, err
|
|
||||||
}
|
|
||||||
err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
|
err, paths := verifyFFProjectCompliance(config, utils, sys, project, projectVersion, filterSet, influx, auditStatus)
|
||||||
reports = append(reports, paths...)
|
reports = append(reports, paths...)
|
||||||
return reports, err
|
return reports, err
|
||||||
|
@@ -24,6 +24,7 @@ type Runs struct {
|
|||||||
type Results struct {
|
type Results struct {
|
||||||
RuleID string `json:"ruleId"`
|
RuleID string `json:"ruleId"`
|
||||||
RuleIndex int `json:"ruleIndex"`
|
RuleIndex int `json:"ruleIndex"`
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
Level string `json:"level,omitempty"`
|
Level string `json:"level,omitempty"`
|
||||||
Message *Message `json:"message,omitempty"`
|
Message *Message `json:"message,omitempty"`
|
||||||
AnalysisTarget *ArtifactLocation `json:"analysisTarget,omitempty"`
|
AnalysisTarget *ArtifactLocation `json:"analysisTarget,omitempty"`
|
||||||
@@ -102,14 +103,16 @@ type SarifProperties struct {
|
|||||||
// Tool these structs are relevant to the Tool object
|
// Tool these structs are relevant to the Tool object
|
||||||
type Tool struct {
|
type Tool struct {
|
||||||
Driver Driver `json:"driver"`
|
Driver Driver `json:"driver"`
|
||||||
|
Extensions []Driver `json:"extensions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver meta information for the scan and tool context
|
// Driver meta information for the scan and tool context
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
GUID string `json:"guid,omitempty"`
|
||||||
InformationUri string `json:"informationUri,omitempty"`
|
InformationUri string `json:"informationUri,omitempty"`
|
||||||
Rules []SarifRule `json:"rules"`
|
Rules []SarifRule `json:"rules,omitempty"`
|
||||||
SupportedTaxonomies []SupportedTaxonomies `json:"supportedTaxonomies,omitempty"`
|
SupportedTaxonomies []SupportedTaxonomies `json:"supportedTaxonomies,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +194,8 @@ type SupportedTaxonomies struct {
|
|||||||
type DefaultConfiguration struct {
|
type DefaultConfiguration struct {
|
||||||
Properties DefaultProperties `json:"properties,omitempty"`
|
Properties DefaultProperties `json:"properties,omitempty"`
|
||||||
Level string `json:"level,omitempty"` //This exists in the template, but not sure how it is populated. TODO.
|
Level string `json:"level,omitempty"` //This exists in the template, but not sure how it is populated. TODO.
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
Rank float64 `json:"rank,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultProperties
|
// DefaultProperties
|
||||||
@@ -223,6 +228,7 @@ type SarifRuleProperties struct {
|
|||||||
Probability string `json:"probability,omitempty"`
|
Probability string `json:"probability,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Precision string `json:"precision,omitempty"`
|
Precision string `json:"precision,omitempty"`
|
||||||
|
SecuritySeverity string `json:"security-severity,omitempty"` //used by GHAS to defined the tag (low,medium,high)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invocations These structs are relevant to the Invocations object
|
// Invocations These structs are relevant to the Invocations object
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/piper-validation/fortify-client-go/models"
|
"github.com/piper-validation/fortify-client-go/models"
|
||||||
@@ -112,14 +113,14 @@ type ClassInfo struct {
|
|||||||
Type string `xml:"Type"`
|
Type string `xml:"Type"`
|
||||||
Subtype string `xml:"Subtype,omitempty"`
|
Subtype string `xml:"Subtype,omitempty"`
|
||||||
AnalyzerName string `xml:"AnalyzerName"`
|
AnalyzerName string `xml:"AnalyzerName"`
|
||||||
DefaultSeverity string `xml:"DefaultSeverity"`
|
DefaultSeverity float64 `xml:"DefaultSeverity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstanceInfo
|
// InstanceInfo
|
||||||
type InstanceInfo struct {
|
type InstanceInfo struct {
|
||||||
XMLName xml.Name `xml:"InstanceInfo"`
|
XMLName xml.Name `xml:"InstanceInfo"`
|
||||||
InstanceID string `xml:"InstanceID"`
|
InstanceID string `xml:"InstanceID"`
|
||||||
InstanceSeverity string `xml:"InstanceSeverity"`
|
InstanceSeverity float64 `xml:"InstanceSeverity"`
|
||||||
Confidence string `xml:"Confidence"`
|
Confidence string `xml:"Confidence"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,8 +581,18 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Subtype)
|
idArray = append(idArray, fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.Subtype)
|
||||||
}
|
}
|
||||||
result.RuleID = "fortify-" + strings.Join(idArray, "/")
|
result.RuleID = "fortify-" + strings.Join(idArray, "/")
|
||||||
// end handle result
|
result.Kind = "fail" // Default value, Level must not be set if kind is not fail
|
||||||
result.Level = "none" //TODO
|
// This is an "easy" treatment of result.Level. It does not follow the spec exactly, but the idea is there
|
||||||
|
// An exact processing algorithm can be found here https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317648
|
||||||
|
if fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity >= 3.0 {
|
||||||
|
result.Level = "error"
|
||||||
|
} else if fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity >= 1.5 {
|
||||||
|
result.Level = "warning"
|
||||||
|
} else if fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity < 1.5 {
|
||||||
|
result.Level = "note"
|
||||||
|
} else {
|
||||||
|
result.Level = "none"
|
||||||
|
}
|
||||||
//get message
|
//get message
|
||||||
for j := 0; j < len(fvdl.Description); j++ {
|
for j := 0; j < len(fvdl.Description); j++ {
|
||||||
if fvdl.Description[j].ClassID == fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.ClassID {
|
if fvdl.Description[j].ClassID == fvdl.Vulnerabilities.Vulnerability[i].ClassInfo.ClassID {
|
||||||
@@ -682,7 +693,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
location = *tfloc
|
location = *tfloc
|
||||||
//set Kinds
|
//set Kinds
|
||||||
threadFlowLocation.Location = tfloc
|
threadFlowLocation.Location = tfloc
|
||||||
threadFlowLocation.Kinds = append(threadFlowLocation.Kinds, "review") //TODO
|
//threadFlowLocation.Kinds = append(threadFlowLocation.Kinds, "review") //TODO
|
||||||
threadFlowLocation.Index = 0 // to be safe?
|
threadFlowLocation.Index = 0 // to be safe?
|
||||||
tfla = append(tfla, threadFlowLocation)
|
tfla = append(tfla, threadFlowLocation)
|
||||||
|
|
||||||
@@ -770,7 +781,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
|
|
||||||
//handle properties
|
//handle properties
|
||||||
prop := new(format.SarifProperties)
|
prop := new(format.SarifProperties)
|
||||||
prop.InstanceSeverity = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity
|
prop.InstanceSeverity = strconv.FormatFloat(fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceSeverity, 'f', 1, 64)
|
||||||
prop.Confidence = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.Confidence
|
prop.Confidence = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.Confidence
|
||||||
prop.InstanceID = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID
|
prop.InstanceID = fvdl.Vulnerabilities.Vulnerability[i].InstanceInfo.InstanceID
|
||||||
//Get the audit data
|
//Get the audit data
|
||||||
@@ -836,12 +847,15 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
sarifRule.Name = strings.Join(nameArray, "")
|
sarifRule.Name = strings.Join(nameArray, "")
|
||||||
defaultConfig := new(format.DefaultConfiguration)
|
defaultConfig := new(format.DefaultConfiguration)
|
||||||
defaultConfig.Level = "warning" // Default value
|
defaultConfig.Level = "warning" // Default value
|
||||||
defaultConfig.Properties.DefaultSeverity = fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.DefaultSeverity
|
defaultConfig.Enabled = true // Default value
|
||||||
|
defaultConfig.Rank = -1.0 // Default value
|
||||||
|
defaultConfig.Properties.DefaultSeverity = strconv.FormatFloat(fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.DefaultSeverity, 'f', 1, 64)
|
||||||
sarifRule.DefaultConfiguration = defaultConfig
|
sarifRule.DefaultConfiguration = defaultConfig
|
||||||
|
|
||||||
//Descriptions
|
//Descriptions
|
||||||
for j := 0; j < len(fvdl.Description); j++ {
|
for j := 0; j < len(fvdl.Description); j++ {
|
||||||
if fvdl.Description[j].ClassID == sarifRule.GUID {
|
if fvdl.Description[j].ClassID == sarifRule.GUID {
|
||||||
|
//rawAbstract := strings.Join(idArray, "/")
|
||||||
rawAbstract := unescapeXML(fvdl.Description[j].Abstract.Text)
|
rawAbstract := unescapeXML(fvdl.Description[j].Abstract.Text)
|
||||||
rawExplanation := unescapeXML(fvdl.Description[j].Explanation.Text)
|
rawExplanation := unescapeXML(fvdl.Description[j].Explanation.Text)
|
||||||
|
|
||||||
@@ -858,7 +872,7 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
}
|
}
|
||||||
// If Description has a CustomDescription, add it for good measure
|
// If Description has a CustomDescription, add it for good measure
|
||||||
if fvdl.Description[j].CustomDescription.RuleID != "" {
|
if fvdl.Description[j].CustomDescription.RuleID != "" {
|
||||||
rawExplanation = rawExplanation + "\n;" + fvdl.Description[j].CustomDescription.Explanation.Text
|
rawExplanation = rawExplanation + " \n; " + unescapeXML(fvdl.Description[j].CustomDescription.Explanation.Text)
|
||||||
}
|
}
|
||||||
sd := new(format.Message)
|
sd := new(format.Message)
|
||||||
sd.Text = rawAbstract
|
sd.Text = rawAbstract
|
||||||
@@ -900,8 +914,8 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var ruleProp *format.SarifRuleProperties
|
var ruleProp *format.SarifRuleProperties
|
||||||
if len(propArray) != 0 {
|
|
||||||
ruleProp = new(format.SarifRuleProperties)
|
ruleProp = new(format.SarifRuleProperties)
|
||||||
|
if len(propArray) != 0 {
|
||||||
for j := 0; j < len(propArray); j++ {
|
for j := 0; j < len(propArray); j++ {
|
||||||
if propArray[j][0] == "Accuracy" {
|
if propArray[j][0] == "Accuracy" {
|
||||||
ruleProp.Accuracy = propArray[j][1]
|
ruleProp.Accuracy = propArray[j][1]
|
||||||
@@ -912,11 +926,29 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add each part of the "name" in the tags
|
||||||
|
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom != "" {
|
||||||
|
ruleProp.Tags = append(ruleProp.Tags, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Kingdom)
|
||||||
|
}
|
||||||
|
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type != "" {
|
||||||
|
ruleProp.Tags = append(ruleProp.Tags, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Type)
|
||||||
|
}
|
||||||
|
if fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype != "" {
|
||||||
|
ruleProp.Tags = append(ruleProp.Tags, fvdl.Vulnerabilities.Vulnerability[j].ClassInfo.Subtype)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the SecuritySeverity parameter for GHAS tagging
|
||||||
|
ruleProp.SecuritySeverity = strconv.FormatFloat(2*fvdl.Vulnerabilities.Vulnerability[j].InstanceInfo.InstanceSeverity, 'f', 1, 64)
|
||||||
|
|
||||||
sarifRule.Properties = ruleProp
|
sarifRule.Properties = ruleProp
|
||||||
|
|
||||||
//relationships: will most likely require some expansion
|
//relationships: will most likely require some expansion
|
||||||
//One relationship per CWE id
|
//One relationship per CWE id
|
||||||
for j := 0; j < len(cweIds); j++ {
|
for j := 0; j < len(cweIds); j++ {
|
||||||
|
if cweIds[j] == "None" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
sarifRule.Properties.Tags = append(sarifRule.Properties.Tags, "external/cwe/cwe-"+cweIds[j])
|
sarifRule.Properties.Tags = append(sarifRule.Properties.Tags, "external/cwe/cwe-"+cweIds[j])
|
||||||
|
|
||||||
rls := *new(format.Relationships)
|
rls := *new(format.Relationships)
|
||||||
@@ -945,6 +977,15 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
sTax.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
|
sTax.Guid = "25F72D7E-8A92-459D-AD67-64853F788765"
|
||||||
tool.Driver.SupportedTaxonomies = append(tool.Driver.SupportedTaxonomies, sTax)
|
tool.Driver.SupportedTaxonomies = append(tool.Driver.SupportedTaxonomies, sTax)
|
||||||
|
|
||||||
|
//Add additional rulepacks
|
||||||
|
for pack := 0; pack < len(fvdl.EngineData.RulePacks); pack++ {
|
||||||
|
extension := *new(format.Driver)
|
||||||
|
extension.Name = fvdl.EngineData.RulePacks[pack].Name
|
||||||
|
extension.Version = fvdl.EngineData.RulePacks[pack].Version
|
||||||
|
extension.GUID = fvdl.EngineData.RulePacks[pack].RulePackID
|
||||||
|
tool.Extensions = append(tool.Extensions, extension)
|
||||||
|
}
|
||||||
|
|
||||||
//Finalize: tool
|
//Finalize: tool
|
||||||
sarif.Runs[0].Tool = tool
|
sarif.Runs[0].Tool = tool
|
||||||
|
|
||||||
@@ -1117,6 +1158,14 @@ func Parse(sys System, project *models.Project, projectVersion *models.ProjectVe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string, sys System, project *models.Project, projectVersion *models.ProjectVersion, auditData []*models.ProjectVersionIssue, filterSet *models.FilterSet, oneRequestPerIssue bool) error {
|
func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string, sys System, project *models.Project, projectVersion *models.ProjectVersion, auditData []*models.ProjectVersionIssue, filterSet *models.FilterSet, oneRequestPerIssue bool) error {
|
||||||
|
// Set default values
|
||||||
|
ruleProp.Audited = false
|
||||||
|
ruleProp.FortifyCategory = "Unknown"
|
||||||
|
ruleProp.ToolSeverity = "Unknown"
|
||||||
|
ruleProp.ToolState = "Unreviewed"
|
||||||
|
ruleProp.ToolSeverityIndex = 0
|
||||||
|
ruleProp.ToolStateIndex = 0
|
||||||
|
// These default values allow for the property bag to be filled even if an error happens later. They all should be overwritten by a normal course of the progrma.
|
||||||
if sys == nil {
|
if sys == nil {
|
||||||
err := errors.New("no system instance, lookup impossible for " + issueInstanceID)
|
err := errors.New("no system instance, lookup impossible for " + issueInstanceID)
|
||||||
return err
|
return err
|
||||||
@@ -1144,6 +1193,17 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
|
|||||||
if len(data) != 1 { //issueInstanceID is supposedly unique so len(data) = 1
|
if len(data) != 1 { //issueInstanceID is supposedly unique so len(data) = 1
|
||||||
return errors.New("not exactly 1 issue found, found " + fmt.Sprint(len(data)))
|
return errors.New("not exactly 1 issue found, found " + fmt.Sprint(len(data)))
|
||||||
}
|
}
|
||||||
|
if filterSet != nil {
|
||||||
|
for i := 0; i < len(filterSet.Folders); i++ {
|
||||||
|
if filterSet.Folders[i].GUID == *data[0].FolderGUID {
|
||||||
|
ruleProp.FortifyCategory = filterSet.Folders[i].Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := errors.New("no filter set defined, category will be missing from " + issueInstanceID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
ruleProp.Audited = data[0].Audited
|
ruleProp.Audited = data[0].Audited
|
||||||
ruleProp.ToolSeverity = *data[0].Friority
|
ruleProp.ToolSeverity = *data[0].Friority
|
||||||
switch ruleProp.ToolSeverity {
|
switch ruleProp.ToolSeverity {
|
||||||
@@ -1184,17 +1244,6 @@ func integrateAuditData(ruleProp *format.SarifProperties, issueInstanceID string
|
|||||||
}
|
}
|
||||||
ruleProp.ToolAuditMessage = unescapeXML(*commentData[0].Comment)
|
ruleProp.ToolAuditMessage = unescapeXML(*commentData[0].Comment)
|
||||||
}
|
}
|
||||||
if filterSet != nil {
|
|
||||||
for i := 0; i < len(filterSet.Folders); i++ {
|
|
||||||
if filterSet.Folders[i].GUID == *data[0].FolderGUID {
|
|
||||||
ruleProp.FortifyCategory = filterSet.Folders[i].Name
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := errors.New("no filter set defined, category will be missing from " + issueInstanceID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,9 +1282,9 @@ func handleSnippet(snippetType string, snippet string) string {
|
|||||||
func unescapeXML(input string) string {
|
func unescapeXML(input string) string {
|
||||||
raw := input
|
raw := input
|
||||||
// Post-treat string to change the XML escaping generated by Unmarshal
|
// Post-treat string to change the XML escaping generated by Unmarshal
|
||||||
|
raw = strings.ReplaceAll(raw, "&", "&")
|
||||||
raw = strings.ReplaceAll(raw, "<", "<")
|
raw = strings.ReplaceAll(raw, "<", "<")
|
||||||
raw = strings.ReplaceAll(raw, ">", ">")
|
raw = strings.ReplaceAll(raw, ">", ">")
|
||||||
raw = strings.ReplaceAll(raw, "&", "&")
|
|
||||||
raw = strings.ReplaceAll(raw, "'", "'")
|
raw = strings.ReplaceAll(raw, "'", "'")
|
||||||
raw = strings.ReplaceAll(raw, """, "\"")
|
raw = strings.ReplaceAll(raw, """, "\"")
|
||||||
return raw
|
return raw
|
||||||
|
Reference in New Issue
Block a user