Merge branch '9-perform-cleaning-code' into 'master'

Resolve "Perform cleaning code"

Closes #9

See merge request bartlomiej.pluta/esa-tool!9
This commit is contained in:
Bartłomiej Pluta
2019-04-05 08:28:41 +00:00
16 changed files with 136 additions and 57 deletions

View File

@@ -4,7 +4,6 @@ import com.bartek.esa.analyser.apk.ApkAnalyser;
import com.bartek.esa.analyser.source.SourceAnalyser; import com.bartek.esa.analyser.source.SourceAnalyser;
import com.bartek.esa.cli.model.CliArgsOptions; import com.bartek.esa.cli.model.CliArgsOptions;
import com.bartek.esa.cli.parser.CliArgsParser; import com.bartek.esa.cli.parser.CliArgsParser;
import com.bartek.esa.core.model.enumeration.Severity;
import com.bartek.esa.core.model.object.Issue; import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.di.DaggerDependencyInjector; import com.bartek.esa.di.DaggerDependencyInjector;
import com.bartek.esa.dispatcher.dispatcher.MethodDispatcher; import com.bartek.esa.dispatcher.dispatcher.MethodDispatcher;
@@ -12,7 +11,7 @@ import com.bartek.esa.dispatcher.model.DispatcherActions;
import com.bartek.esa.formatter.provider.FormatterProvider; import com.bartek.esa.formatter.provider.FormatterProvider;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.Set;
public class EsaMain { public class EsaMain {
private final CliArgsParser cliArgsParser; private final CliArgsParser cliArgsParser;
@@ -37,14 +36,14 @@ public class EsaMain {
.build(); .build();
CliArgsOptions options = cliArgsParser.parse(args); CliArgsOptions options = cliArgsParser.parse(args);
List<Issue> issues = methodDispatcher.dispatchMethod(options, dispatcherActions); Set<Issue> issues = methodDispatcher.dispatchMethod(options, dispatcherActions);
formatterProvider.provide(options).format(issues); formatterProvider.provide(options).format(issues);
exitWithErrorIfAnyIssueIsAnError(issues); exitWithErrorIfAnyIssueIsAnError(issues);
} }
private void exitWithErrorIfAnyIssueIsAnError(List<Issue> issues) { private void exitWithErrorIfAnyIssueIsAnError(Set<Issue> issues) {
if(issues.stream().anyMatch(i -> i.getSeverity() == Severity.ERROR)) { if(issues.stream().anyMatch(i -> i.getSeverity().isExitWithError())) {
System.exit(1); System.exit(1);
} }
} }

View File

@@ -4,6 +4,9 @@ import com.bartek.esa.analyser.core.Analyser;
import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.decompiler.decompiler.Decompiler; import com.bartek.esa.decompiler.decompiler.Decompiler;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.cleaner.FileCleaner;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.file.provider.FileProvider; import com.bartek.esa.file.provider.FileProvider;
import java.io.File; import java.io.File;
@@ -15,17 +18,29 @@ public class ApkAnalyser extends Analyser {
private static final String LAYOUT_GLOB = "**/" + Decompiler.XML_FILES_DIR + "/**/layout*/*.xml"; private static final String LAYOUT_GLOB = "**/" + Decompiler.XML_FILES_DIR + "/**/layout*/*.xml";
private final Decompiler decompiler; private final Decompiler decompiler;
private final FileCleaner fileCleaner;
private final GlobMatcher globMatcher;
public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler) { public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
super(pluginExecutor, plugins, fileProvider); super(pluginExecutor, plugins, fileProvider);
this.decompiler = decompiler; this.decompiler = decompiler;
this.fileCleaner = fileCleaner;
this.globMatcher = globMatcher;
} }
@Override @Override
protected String prepareSources(String source) { protected String prepareSources(String source) {
checkIfSourceIsApkFile(source);
System.out.println("Decompiling APK...");
return decompiler.decompile(new File(source)).getAbsolutePath(); return decompiler.decompile(new File(source)).getAbsolutePath();
} }
private void checkIfSourceIsApkFile(String source) {
if (!globMatcher.fileMatchesGlobPattern(new File(source), "**/*.apk")) {
throw new EsaException("Provided source is not *.apk file. Interrupting...");
}
}
@Override @Override
protected String getAndroidManifestGlob() { protected String getAndroidManifestGlob() {
return ANDROID_MANIFEST_GLOB; return ANDROID_MANIFEST_GLOB;
@@ -40,4 +55,9 @@ public class ApkAnalyser extends Analyser {
protected String getLayoutGlob() { protected String getLayoutGlob() {
return LAYOUT_GLOB; return LAYOUT_GLOB;
} }
@Override
protected void performCleaning(String source) {
fileCleaner.deleteRecursively(new File(source));
}
} }

View File

@@ -8,7 +8,6 @@ import com.bartek.esa.file.provider.FileProvider;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -25,13 +24,15 @@ public abstract class Analyser {
this.fileProvider = fileProvider; this.fileProvider = fileProvider;
} }
public List<Issue> analyse(String source, Set<String> pluginCodes, Set<String> excludeCodes) { public Set<Issue> analyse(String source, Set<String> pluginCodes, Set<String> excludeCodes) {
String newSource = prepareSources(source); String newSource = prepareSources(source);
File manifest = getManifest(newSource); File manifest = getManifest(newSource);
Set<File> files = getFiles(newSource); Set<File> files = getFiles(newSource);
Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes); Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes);
return pluginExecutor.executeForFiles(manifest, files, selectedPlugins); Set<Issue> issues = pluginExecutor.executeForFiles(manifest, files, selectedPlugins);
performCleaning(newSource);
return issues;
} }
protected abstract String prepareSources(String source); protected abstract String prepareSources(String source);
@@ -42,6 +43,8 @@ public abstract class Analyser {
protected abstract String getLayoutGlob(); protected abstract String getLayoutGlob();
protected abstract void performCleaning(String source);
private File getManifest(String source) { private File getManifest(String source) {
Set<File> manifests = fileProvider.getGlobMatchedFiles(source, getAndroidManifestGlob()); Set<File> manifests = fileProvider.getGlobMatchedFiles(source, getAndroidManifestGlob());

View File

@@ -5,6 +5,8 @@ import com.bartek.esa.analyser.source.SourceAnalyser;
import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.decompiler.decompiler.Decompiler; import com.bartek.esa.decompiler.decompiler.Decompiler;
import com.bartek.esa.file.cleaner.FileCleaner;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.file.provider.FileProvider; import com.bartek.esa.file.provider.FileProvider;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -20,7 +22,7 @@ public class AnalyserModule {
} }
@Provides @Provides
public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler) { public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler); return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher);
} }
} }

View File

@@ -3,8 +3,10 @@ package com.bartek.esa.analyser.source;
import com.bartek.esa.analyser.core.Analyser; import com.bartek.esa.analyser.core.Analyser;
import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.provider.FileProvider; import com.bartek.esa.file.provider.FileProvider;
import java.io.File;
import java.util.Set; import java.util.Set;
public class SourceAnalyser extends Analyser { public class SourceAnalyser extends Analyser {
@@ -18,9 +20,16 @@ public class SourceAnalyser extends Analyser {
@Override @Override
protected String prepareSources(String source) { protected String prepareSources(String source) {
checkIfSourceIsDirectory(source);
return source; return source;
} }
private void checkIfSourceIsDirectory(String source) {
if (!new File(source).isDirectory()) {
throw new EsaException("Provided source is not a directory. Interrupting...");
}
}
@Override @Override
protected String getAndroidManifestGlob() { protected String getAndroidManifestGlob() {
return ANDROID_MANIFEST_GLOB; return ANDROID_MANIFEST_GLOB;
@@ -35,4 +44,9 @@ public class SourceAnalyser extends Analyser {
protected String getLayoutGlob() { protected String getLayoutGlob() {
return LAYOUT_GLOB; return LAYOUT_GLOB;
} }
@Override
protected void performCleaning(String source) {
// do nothing
}
} }

View File

@@ -5,11 +5,11 @@ import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List; import java.util.Set;
public abstract class BasePlugin implements Plugin { public abstract class BasePlugin implements Plugin {
private List<Issue> issues = new ArrayList<>(); private Set<Issue> issues = new HashSet<>();
private Document manifest; private Document manifest;
private File file; private File file;
@@ -21,7 +21,7 @@ public abstract class BasePlugin implements Plugin {
} }
@Override @Override
public List<Issue> runForIssues() { public Set<Issue> runForIssues() {
run(file); run(file);
return issues; return issues;
} }
@@ -45,6 +45,10 @@ public abstract class BasePlugin implements Plugin {
issues.add(issue); issues.add(issue);
} }
protected void addIssue(Issue issue) {
issues.add(issue);
}
protected File getOriginalFile() { protected File getOriginalFile() {
return file; return file;
} }

View File

@@ -1,6 +1,7 @@
package com.bartek.esa.core.archetype; package com.bartek.esa.core.archetype;
import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.model.enumeration.Severity;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.core.xml.XmlHelper; import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.error.EsaException; import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.matcher.GlobMatcher; import com.bartek.esa.file.matcher.GlobMatcher;
@@ -43,7 +44,14 @@ public abstract class JavaPlugin extends BasePlugin {
Node packageValue = root.getAttributes().getNamedItem("package"); Node packageValue = root.getAttributes().getNamedItem("package");
if(packageValue == null) { if(packageValue == null) {
addIssue(Severity.ERROR, ".PACKAGE_LACK", null, null); Issue issue = Issue.builder()
.issuer(JavaPlugin.class)
.descriptionCode(".NO_PACKAGE")
.severity(Severity.ERROR)
.build();
addIssue(issue);
return false; return false;
} }

View File

@@ -4,10 +4,10 @@ import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import java.io.File; import java.io.File;
import java.util.List; import java.util.Set;
public interface Plugin { public interface Plugin {
boolean supports(File file); boolean supports(File file);
void update(File file, Document manifest); void update(File file, Document manifest);
List<Issue> runForIssues(); Set<Issue> runForIssues();
} }

View File

@@ -7,10 +7,9 @@ import org.w3c.dom.Document;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.util.List;
import java.util.Set; import java.util.Set;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet;
public class PluginExecutor { public class PluginExecutor {
private final XmlHelper xmlHelper; private final XmlHelper xmlHelper;
@@ -20,20 +19,20 @@ public class PluginExecutor {
this.xmlHelper = xmlHelper; this.xmlHelper = xmlHelper;
} }
public List<Issue> executeForFiles(File manifest, Set<File> files, Set<Plugin> plugins) { public Set<Issue> executeForFiles(File manifest, Set<File> files, Set<Plugin> plugins) {
return files.stream() return files.stream()
.map(file -> executeForFile(manifest, file, plugins)) .map(file -> executeForFile(manifest, file, plugins))
.flatMap(List::stream) .flatMap(Set::stream)
.collect(toList()); .collect(toSet());
} }
private List<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins) { private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins) {
Document xmlManifest = xmlHelper.parseXml(manifest); Document xmlManifest = xmlHelper.parseXml(manifest);
return plugins.stream() return plugins.stream()
.peek(plugin -> plugin.update(file, xmlManifest)) .peek(plugin -> plugin.update(file, xmlManifest))
.filter(plugin -> plugin.supports(file)) .filter(plugin -> plugin.supports(file))
.map(Plugin::runForIssues) .map(Plugin::runForIssues)
.flatMap(List::stream) .flatMap(Set::stream)
.collect(toList()); .collect(toSet());
} }
} }

View File

@@ -1,6 +1,17 @@
package com.bartek.esa.core.model.enumeration; package com.bartek.esa.core.model.enumeration;
import lombok.Getter;
@Getter
public enum Severity { public enum Severity {
WARNING, INFO(false),
ERROR WARNING(false),
ERROR(true),
VULNERABILITY(true);
private final boolean exitWithError;
Severity(boolean exitWithError) {
this.exitWithError = exitWithError;
}
} }

View File

@@ -6,7 +6,7 @@ import com.bartek.esa.dispatcher.model.DispatcherActions;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.Set;
public class MethodDispatcher { public class MethodDispatcher {
@@ -15,7 +15,7 @@ public class MethodDispatcher {
} }
public List<Issue> dispatchMethod(CliArgsOptions options, DispatcherActions actions) { public Set<Issue> dispatchMethod(CliArgsOptions options, DispatcherActions actions) {
if(options.isSourceAnalysis()) { if(options.isSourceAnalysis()) {
return actions.getSourceAnalysis().perform( return actions.getSourceAnalysis().perform(
options.getSourceAnalysisDirectory(), options.getSourceAnalysisDirectory(),
@@ -32,6 +32,6 @@ public class MethodDispatcher {
); );
} }
return Collections.emptyList(); return Collections.emptySet();
} }
} }

View File

@@ -2,11 +2,10 @@ package com.bartek.esa.dispatcher.model;
import com.bartek.esa.core.model.object.Issue; import com.bartek.esa.core.model.object.Issue;
import java.util.List;
import java.util.Set; import java.util.Set;
@FunctionalInterface @FunctionalInterface
public interface Action { public interface Action {
List<Issue> perform(String source, Set<String> plugins, Set<String> excludes); Set<Issue> perform(String source, Set<String> plugins, Set<String> excludes);
} }

View File

@@ -2,8 +2,8 @@ package com.bartek.esa.formatter.archetype;
import com.bartek.esa.core.model.object.Issue; import com.bartek.esa.core.model.object.Issue;
import java.util.List; import java.util.Set;
public interface Formatter { public interface Formatter {
void format(List<Issue> issues); void format(Set<Issue> issues);
} }

View File

@@ -7,8 +7,9 @@ import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiConsole;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.fusesource.jansi.Ansi.Color.*; import static org.fusesource.jansi.Ansi.Color.*;
@@ -24,9 +25,9 @@ public class ColorFormatter implements Formatter {
} }
@Override @Override
public void format(List<Issue> issues) { public void format(Set<Issue> issues) {
AnsiConsole.systemInstall(); AnsiConsole.systemInstall();
if(issues.isEmpty()) { if (issues.isEmpty()) {
Ansi noIssuesFound = ansi().fg(GREEN).a("No issues found.").reset(); Ansi noIssuesFound = ansi().fg(GREEN).a("No issues found.").reset();
System.out.println(noIssuesFound); System.out.println(noIssuesFound);
return; return;
@@ -52,29 +53,36 @@ public class ColorFormatter implements Formatter {
private Ansi appendDescription(Issue issue, Ansi ansi) { private Ansi appendDescription(Issue issue, Ansi ansi) {
String description = descriptionProvider.getDescriptionForIssue(issue); String description = descriptionProvider.getDescriptionForIssue(issue);
String[] lines = description.split("\n");
String firstLine = lines[0];
String theRestOfLines = lines.length > 1 ? "\n" + String.join("\n", Arrays.copyOfRange(lines, 1, lines.length)) : "";
return ansi return ansi
.fg(getColorForSeverity(issue)) .fg(getColorForSeverity(issue))
.a(issue.getSeverity().name()) .a(issue.getSeverity().name())
.reset() .reset()
.a(": ").a(description) .a(": ").a(firstLine)
.fgBrightBlack().a(theRestOfLines)
.reset()
.a("\n"); .a("\n");
} }
private Ansi appendFile(Issue issue, Ansi ansi) { private Ansi appendFile(Issue issue, Ansi ansi) {
return ansi return Optional.ofNullable(issue.getFile())
.fg(GREEN) .map(file -> ansi
.a("File: ") .fg(BLUE)
.reset() .a("File: ")
.a(issue.getFile().getAbsolutePath()) .reset()
.a("\n"); .a(file.getAbsolutePath())
.a("\n"))
.orElse(ansi);
} }
private Ansi appendLine(Issue issue, Ansi ansi) { private Ansi appendLine(Issue issue, Ansi ansi) {
Optional.ofNullable(issue.getLine()) Optional.ofNullable(issue.getLine())
.ifPresent(line -> { .ifPresent(line -> {
ansi ansi
.fg(BLUE) .fg(CYAN)
.a("Line"); .a("Line");
Optional.ofNullable(issue.getLineNumber()).ifPresentOrElse( Optional.ofNullable(issue.getLineNumber()).ifPresentOrElse(
number -> ansi.a(" ").a(number).a(": "), number -> ansi.a(" ").a(number).a(": "),
@@ -86,9 +94,15 @@ public class ColorFormatter implements Formatter {
} }
private Ansi.Color getColorForSeverity(Issue issue) { private Ansi.Color getColorForSeverity(Issue issue) {
switch(issue.getSeverity()) { switch (issue.getSeverity()) {
case WARNING: return YELLOW; case INFO:
case ERROR: return RED; return GREEN;
case WARNING:
return YELLOW;
case ERROR:
return MAGENTA;
case VULNERABILITY:
return RED;
} }
return RED; return RED;

View File

@@ -5,8 +5,8 @@ import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.formatter.archetype.Formatter; import com.bartek.esa.formatter.archetype.Formatter;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class SimpleFormatter implements Formatter { public class SimpleFormatter implements Formatter {
@@ -18,8 +18,8 @@ public class SimpleFormatter implements Formatter {
} }
@Override @Override
public void format(List<Issue> issues) { public void format(Set<Issue> issues) {
if(issues.isEmpty()) { if (issues.isEmpty()) {
System.out.println("No issues found."); System.out.println("No issues found.");
return; return;
} }
@@ -50,10 +50,13 @@ public class SimpleFormatter implements Formatter {
} }
private void appendFile(Issue issue, StringBuilder format) { private void appendFile(Issue issue, StringBuilder format) {
format Optional.ofNullable(issue.getFile())
.append("File: ") .ifPresent(file ->
.append(issue.getFile().getAbsolutePath()) format
.append("\n"); .append("File: ")
.append(file.getAbsolutePath())
.append("\n")
);
} }
private void appendLine(Issue issue, StringBuilder format) { private void appendLine(Issue issue, StringBuilder format) {

View File

@@ -1 +1,4 @@
com.bartek.esa.core.plugin.JavaPlugin.PACKAGE_LACK=There is no package defined in AndroidManifest.xml file. Please check fix to use this tool. com.bartek.esa.core.archetype.JavaPlugin.NO_PACKAGE=There is no package defined in AndroidManifest.xml file. \n\
Package should be defined as attribute of <manifest> tag.\n\
For example: <manifest package="com.bartek.esa.test">\n\
Please fix it to use this tool.