Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

/**
* <p>This class provides the {@link #main(String[])} entrypoint into the application,
* and also registers some GraalVM features, allowing the application to run properly
* and also registers some GraalVM features, allowing the application to run properly
* as GraalVM native images.</p>
*
*
* @author Ruud Senden
*/
public class FortifyCLI {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;

Expand All @@ -53,8 +55,7 @@
import com.fortify.cli.aviator.fpr.utils.SourceDecoders;
import com.fortify.cli.aviator.fpr.utils.SourceEncoder;
import com.fortify.cli.aviator.fpr.utils.SourceEncoder.SourceEncodeException;
import com.fortify.cli.aviator.util.FprHandle;
import com.fortify.cli.aviator.util.FuzzyContextSearcher;
import com.fortify.cli.aviator.util.*;

public class RemediationProcessor {
private static final Logger LOG = LoggerFactory.getLogger(RemediationProcessor.class);
Expand All @@ -64,10 +65,10 @@ public class RemediationProcessor {
private final String sourceCodeDirectory;
private final ISourceDecoder sourceDecoder;

public record RemediationMetric(int totalRemediations, int appliedRemediations, int skippedRemediations, Set<String> modifiedFiles,
public record RemediationMetric(int totalRemediations, int appliedRemediations, int identicalRemediations,int skippedRemediations, Set<String> modifiedFiles,
Map<String, Integer> skippedByReason) {
public RemediationMetric(int totalRemediations, int appliedRemediations, int skippedRemediations, Set<String> modifiedFiles) {
this(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, Map.of());
public RemediationMetric(int totalRemediations, int appliedRemediations,int identicalRemediations,int skippedRemediations, Set<String> modifiedFiles) {
this(totalRemediations, appliedRemediations,identicalRemediations, skippedRemediations, modifiedFiles, Map.of());
}
}

Expand All @@ -78,6 +79,8 @@ private record PendingFileWrite(String filename, Path filePath, String content,

private record RollbackFileWrite(String filename, Path filePath, byte[] originalBytes) {}

private record RemediationKey(String fileName, Path filePath,int lineFrom,int lineTo,String comparisonCode){}

private enum SkipReason {
SOURCE_FILE_MISSING("Source file missing"),
SOURCE_FILE_OUTSIDE_SOURCE_DIR("Source file outside source directory"),
Expand Down Expand Up @@ -154,9 +157,11 @@ public RemediationMetric processRemediationXML() {
Document remediationDoc;
int totalRemediations;
int appliedRemediations;
int identicalRemediations = 0;
Set<String> modifiedFiles = new LinkedHashSet<>();
Map<String, Integer> skippedByReason = new LinkedHashMap<>();

Map<RemediationKey, String> remediationLookup = new LinkedHashMap<>();
LOG.debug("in the processRemediationXML method");
// Sanitize and normalize the base source directory path once.
String trimmedSourceDir = sourceCodeDirectory.trim();
if (trimmedSourceDir.length() > 1 &&
Expand All @@ -183,10 +188,81 @@ public RemediationMetric processRemediationXML() {
totalRemediations = remediationNodes.getLength();
LOG.debug("Loaded {} remediation entries from {}", totalRemediations, remediationPath);
appliedRemediations = 0;

for (int i = 0; i < remediationNodes.getLength(); i++) {
Element remediation = (Element) remediationNodes.item(i);
if (processRemediation(remediation, sourceBasePath, fvdlMetadata, modifiedFiles, skippedByReason)) {
LOG.debug("........................");
Element remediation =
(Element) remediationNodes.item(i);

String instanceId =
remediation.getAttribute("instanceId");
LOG.debug("remediation{}",instanceId);

List<RemediationKey> remediationKeys =
createRemediationKeys(
remediation,
sourceBasePath);

LOG.debug(
"Remediation {} generated {} lookup key(s): {}",
instanceId,
remediationKeys.size(),
remediationKeys);

String identicalInstanceId = null;

/*
* A remediation is identical only when all of its changes
* match an existing remediation.
*/
if (!remediationKeys.isEmpty()) {
for (String existingInstanceId :
new LinkedHashSet<>(remediationLookup.values())) {

List<RemediationKey> existingKeys =
remediationLookup.entrySet().stream()
.filter(entry ->
existingInstanceId.equals(entry.getValue()))
.map(Map.Entry::getKey)
.toList();

if (existingKeys.size() == remediationKeys.size()
&& existingKeys.containsAll(remediationKeys)) {
identicalInstanceId = existingInstanceId;
break;
}
}
}

if (identicalInstanceId != null) {
identicalRemediations++;
appliedRemediations++;

LOG.info(
"Identical found: {}",
identicalInstanceId);

LOG.info(
"Identical Remediation Applied: {} is identical to {}",
instanceId,
identicalInstanceId);

continue;
}

if (processRemediation(
remediation,
sourceBasePath,
fvdlMetadata,
modifiedFiles,
skippedByReason)) {

appliedRemediations++;

for (RemediationKey key : remediationKeys) {
LOG.debug("putting {}",instanceId);
remediationLookup.put(key, instanceId);
}
}
}

Expand All @@ -195,18 +271,22 @@ public RemediationMetric processRemediationXML() {
throw new AviatorTechnicalException("Error processing remediation.xml file.", e);
} catch (AviatorTechnicalException e) {
throw e;

} catch (Exception e) {
LOG.error("Unexpected error processing remediation.xml: {}", remediationPath, e);
throw new AviatorTechnicalException("Unexpected error processing remediations.xml.", e);
}

int skippedRemediations = totalRemediations - appliedRemediations;
LOG.info("Auto-remediation summary: total={}, applied={}, skipped={}", totalRemediations, appliedRemediations, skippedRemediations);
LOG.info("Auto-remediation summary: total={}, applied={},indentical={},skipped={}", totalRemediations, appliedRemediations, identicalRemediations,skippedRemediations);

if (!skippedByReason.isEmpty()) {
LOG.info("Skipped remediations by reason: {}", formatSkippedReasons(skippedByReason));
LOG.info("Skipped remediations by reason: {}",formatSkippedReasons(skippedByReason));
}
return new RemediationMetric(totalRemediations, appliedRemediations, skippedRemediations, modifiedFiles, skippedByReason);
return new RemediationMetric(totalRemediations, appliedRemediations, identicalRemediations, skippedRemediations, modifiedFiles, skippedByReason);
}


private boolean processRemediation(Element remediation, Path sourceBasePath, FVDLMetadata fvdlMetadata,
Set<String> modifiedFiles, Map<String, Integer> skippedByReason) {
String instanceId = remediation.getAttribute("instanceId");
Expand Down Expand Up @@ -577,4 +657,179 @@ private String formatSkippedReasons(Map<String, Integer> skippedByReason) {
skippedByReason.forEach((reason, count) -> parts.add(reason + "=" + count));
return String.join(", ", parts);
}

private RemediationKey createRemediationKey(
Element fileChanges,
Element change,
Path sourceBasePath,
String comparisonCode) {

String fileName = getRequiredElementText(fileChanges, "Filename");
Path filePath = sourceBasePath.resolve(fileName).normalize();

int lineFrom = parseRequiredInt(change, "LineFrom");
int lineTo = parseRequiredInt(change, "LineTo");

return new RemediationKey(
fileName,
filePath,
lineFrom,
lineTo,
comparisonCode
);
}


private String trimBlankLines(String content) {
String[] lines = content.split("\\R", -1);

int start = 0;
int end = lines.length - 1;

while (start <= end && lines[start].isBlank()) {
start++;
}

while (end >= start && lines[end].isBlank()) {
end--;
}

if (start > end) {
return "";
}

return String.join(
System.lineSeparator(),
Arrays.copyOfRange(lines, start, end + 1));
}

private String normalizeProposedCode(String content, String fileName) {
if (content == null) {
return null;
}

String language = FileTypeLanguageMapperUtil.getProgrammingLanguage(
FileUtil.getFileExtension(fileName));

String commentSymbol =
LanguageCommentMapperUtil.getProgrammingLanguageComment(language);

if ("Unknown".equals(commentSymbol)) {
return trimBlankLines(content);
}

String closingToken = commentSymbol.equals("<!--") ? "-->"
: commentSymbol.equals("<%--") ? "--%>"
: null;

Pattern markerPattern = Pattern.compile(
"[ \\t]*" + Pattern.quote(commentSymbol) + " L\\d+"
+ (closingToken != null
? "[ \\t]*" + Pattern.quote(closingToken)
: "")
+ "[ \\t]*$");

String[] lines = content.split("\\R", -1);
StringBuilder result = new StringBuilder();

for (int i = 0; i < lines.length; i++) {
Matcher matcher = markerPattern.matcher(lines[i]);

result.append(
matcher.find()
? lines[i].substring(0, matcher.start())
: lines[i]);

if (i < lines.length - 1) {
result.append(System.lineSeparator());
}
}

return trimBlankLines(result.toString());
}

private String createComparisonCode(String normalizedCode, String fileName) {
if (normalizedCode == null) {
return null;
}

String language = FileTypeLanguageMapperUtil.getProgrammingLanguage(
FileUtil.getFileExtension(fileName));

String commentSymbol =
LanguageCommentMapperUtil.getProgrammingLanguageComment(language);

if ("Unknown".equals(commentSymbol)) {
return normalizedCode.replaceAll("\\s+", "");
}

String comparisonCode = normalizedCode;

// Remove block comments
String closingToken = commentSymbol.equals("<!--") ? "-->"
: commentSymbol.equals("<%--") ? "--%>"
: null;

if (closingToken != null) {
comparisonCode = comparisonCode.replaceAll(
"(?s)" + Pattern.quote(commentSymbol)
+ ".*?" + Pattern.quote(closingToken),
"");
} else if ("//".equals(commentSymbol)) {
comparisonCode = comparisonCode.replaceAll(
"(?m)" + Pattern.quote(commentSymbol) + ".*$",
"");
comparisonCode = comparisonCode.replaceAll(
"(?s)/\\*.*?\\*/",
"");
} else if ("#".equals(commentSymbol)) {
comparisonCode = comparisonCode.replaceAll(
"(?m)" + Pattern.quote(commentSymbol) + ".*$",
"");
}

// Normalize whitespace
return comparisonCode.replaceAll("\\s+", "");
}

private List<RemediationKey> createRemediationKeys(
Element remediation,
Path sourceBasePath) {

List<RemediationKey> keys = new ArrayList<>();

NodeList fileChangesNodes =
remediation.getElementsByTagNameNS(NAMESPACE_URI, "FileChanges");

for (int i = 0; i < fileChangesNodes.getLength(); i++) {
Element fileChanges = (Element) fileChangesNodes.item(i);

NodeList changeNodes =
fileChanges.getElementsByTagNameNS(NAMESPACE_URI, "Change");

for (int j = 0; j < changeNodes.getLength(); j++) {
Element change = (Element) changeNodes.item(j);

String fileName =
getRequiredElementText(fileChanges, "Filename");

String newCode =
getRequiredElementText(change, "NewCode");

String normalizedCode =
normalizeProposedCode(newCode, fileName);

String comparisonCode =
createComparisonCode(normalizedCode, fileName);

keys.add(createRemediationKey(
fileChanges,
change,
sourceBasePath,
comparisonCode));
}
}

return keys;
}
}