diff --git a/build.gradle b/build.gradle index affc8cf..3733dc2 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { group 'com.bartek' version '1.0-SNAPSHOT' -sourceCompatibility = 1.12 +sourceCompatibility = 1.11 repositories { mavenCentral() @@ -25,6 +25,7 @@ dependencies { compile "org.fusesource.jansi:jansi:${jansiVersion}" compile "org.apache.commons:commons-lang3:${commonsLangVersion}" compile "org.apache.commons:commons-text:${commonsTextVersion}" + compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" } jar { diff --git a/dependency-versions.gradle b/dependency-versions.gradle index 2156b4b..26bf82a 100644 --- a/dependency-versions.gradle +++ b/dependency-versions.gradle @@ -8,4 +8,5 @@ ext { jansiVersion = '1.17.1' commonsLangVersion = '3.8.1' commonsTextVersion = '1.6' + jacksonVersion = '2.9.8' } \ No newline at end of file diff --git a/src/main/java/com/bartek/esa/EsaMain.java b/src/main/java/com/bartek/esa/EsaMain.java index ef18130..d686239 100644 --- a/src/main/java/com/bartek/esa/EsaMain.java +++ b/src/main/java/com/bartek/esa/EsaMain.java @@ -2,16 +2,24 @@ package com.bartek.esa; import com.bartek.esa.analyser.apk.ApkAnalyser; import com.bartek.esa.analyser.source.SourceAnalyser; -import com.bartek.esa.cli.model.CliArgsOptions; +import com.bartek.esa.cli.model.object.CliArgsOptions; import com.bartek.esa.cli.parser.CliArgsParser; import com.bartek.esa.core.model.object.Issue; import com.bartek.esa.di.DaggerDependencyInjector; import com.bartek.esa.dispatcher.dispatcher.MethodDispatcher; import com.bartek.esa.dispatcher.model.DispatcherActions; +import com.bartek.esa.error.EsaException; +import com.bartek.esa.formatter.archetype.Formatter; import com.bartek.esa.formatter.provider.FormatterProvider; +import io.vavr.control.Try; import javax.inject.Inject; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; public class EsaMain { @@ -39,9 +47,15 @@ public class EsaMain { CliArgsOptions options = cliArgsParser.parse(args); Set issues = methodDispatcher.dispatchMethod(options, dispatcherActions); Set filteredIssues = filterIssuesBySeverity(options, issues); - formatterProvider.provide(options).format(filteredIssues); + Formatter formatter = formatterProvider.provide(options); + formatter.beforeFormat(); + String output = formatter.format(filteredIssues); + displayOutputOrSaveToFile(options, output); + formatter.afterFormat(); - exitWithErrorIfAnyIssueIsAnError(filteredIssues); + if(options.isStrictMode()) { + exitWithErrorIfAnyIssueIsAnError(filteredIssues); + } } private Set filterIssuesBySeverity(CliArgsOptions options, Set issues) { @@ -50,6 +64,24 @@ public class EsaMain { .collect(Collectors.toSet()); } + private void displayOutputOrSaveToFile(CliArgsOptions options, String output) { + Optional.ofNullable(options.getOut()) + .map(this::getWriter) + .ifPresentOrElse(writeString(output), () -> System.out.println(output)); + } + + private BufferedWriter getWriter(File file) { + return Try.of(() -> new BufferedWriter(new FileWriter(file))) + .getOrElseThrow(EsaException::new); + } + + private Consumer writeString(String string) { + return writer -> Try.run(() -> { + writer.write(string); + writer.close(); + }).getOrElseThrow(EsaException::new); + } + private void exitWithErrorIfAnyIssueIsAnError(Set issues) { if(issues.stream().anyMatch(i -> i.getSeverity().isExitWithError())) { System.exit(1); diff --git a/src/main/java/com/bartek/esa/analyser/apk/ApkAnalyser.java b/src/main/java/com/bartek/esa/analyser/apk/ApkAnalyser.java index 41fd4ce..a9d07d0 100644 --- a/src/main/java/com/bartek/esa/analyser/apk/ApkAnalyser.java +++ b/src/main/java/com/bartek/esa/analyser/apk/ApkAnalyser.java @@ -1,6 +1,7 @@ package com.bartek.esa.analyser.apk; import com.bartek.esa.analyser.core.Analyser; +import com.bartek.esa.context.constructor.ContextConstructor; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.decompiler.archetype.Decompiler; @@ -19,8 +20,8 @@ public class ApkAnalyser extends Analyser { private final FileCleaner fileCleaner; private final GlobMatcher globMatcher; - public ApkAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) { - super(pluginExecutor, plugins, fileProvider); + public ApkAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) { + super(pluginExecutor, plugins, fileProvider, contextConstructor); this.decompiler = decompiler; this.fileCleaner = fileCleaner; this.globMatcher = globMatcher; diff --git a/src/main/java/com/bartek/esa/analyser/core/Analyser.java b/src/main/java/com/bartek/esa/analyser/core/Analyser.java index f571697..b824349 100644 --- a/src/main/java/com/bartek/esa/analyser/core/Analyser.java +++ b/src/main/java/com/bartek/esa/analyser/core/Analyser.java @@ -1,5 +1,7 @@ package com.bartek.esa.analyser.core; +import com.bartek.esa.context.constructor.ContextConstructor; +import com.bartek.esa.context.model.Context; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.core.model.object.Issue; @@ -7,31 +9,34 @@ import com.bartek.esa.error.EsaException; import com.bartek.esa.file.provider.FileProvider; import java.io.File; -import java.util.Collections; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; public abstract class Analyser { private final PluginExecutor pluginExecutor; private final Set plugins; private final FileProvider fileProvider; + private final ContextConstructor contextConstructor; - public Analyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider) { + public Analyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, ContextConstructor contextConstructor) { this.pluginExecutor = pluginExecutor; this.plugins = plugins; this.fileProvider = fileProvider; + this.contextConstructor = contextConstructor; } public Set analyse(String source, Set pluginCodes, Set excludeCodes, boolean debug) { String newSource = prepareSources(source, debug); File manifest = getManifest(newSource); - Set files = getFiles(newSource); - Set selectedPlugins = getPlugins(pluginCodes, excludeCodes); + Set javaSources = getJavaSources(newSource); + Set layoutFiles = getLayoutFiles(newSource); + Context context = contextConstructor.construct(manifest, javaSources, layoutFiles); + + Set selectedPlugins = getPlugins(pluginCodes, excludeCodes); + Set issues = pluginExecutor.executeForContext(context, selectedPlugins, debug); - Set issues = pluginExecutor.executeForFiles(manifest, files, selectedPlugins, debug); performCleaning(newSource, debug); return issues; } @@ -60,14 +65,12 @@ public abstract class Analyser { return (File) (manifests.toArray())[0]; } - private Set getFiles(String source) { - Set javaFiles = fileProvider.getGlobMatchedFiles(source, getJavaGlob()); - Set layoutFiles = fileProvider.getGlobMatchedFiles(source, getLayoutGlob()); - Set androidManifest = Collections.singleton(getManifest(source)); + private Set getJavaSources(String source) { + return fileProvider.getGlobMatchedFiles(source, getJavaGlob()); + } - return Stream.of(javaFiles, androidManifest, layoutFiles) - .flatMap(Set::stream) - .collect(Collectors.toSet()); + private Set getLayoutFiles(String source) { + return fileProvider.getGlobMatchedFiles(source, getLayoutGlob()); } private Set getPlugins(Set pluginCodes, Set excludeCodes) { diff --git a/src/main/java/com/bartek/esa/analyser/di/AnalyserModule.java b/src/main/java/com/bartek/esa/analyser/di/AnalyserModule.java index e8a3b0e..10a9d98 100644 --- a/src/main/java/com/bartek/esa/analyser/di/AnalyserModule.java +++ b/src/main/java/com/bartek/esa/analyser/di/AnalyserModule.java @@ -2,6 +2,7 @@ package com.bartek.esa.analyser.di; import com.bartek.esa.analyser.apk.ApkAnalyser; import com.bartek.esa.analyser.source.SourceAnalyser; +import com.bartek.esa.context.constructor.ContextConstructor; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.decompiler.archetype.Decompiler; @@ -17,12 +18,12 @@ import java.util.Set; public class AnalyserModule { @Provides - public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider) { - return new SourceAnalyser(pluginExecutor, plugins, fileProvider); + public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, ContextConstructor contextConstructor) { + return new SourceAnalyser(pluginExecutor, plugins, fileProvider, contextConstructor); } @Provides - public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) { - return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher); + public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) { + return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher, contextConstructor); } } diff --git a/src/main/java/com/bartek/esa/analyser/source/SourceAnalyser.java b/src/main/java/com/bartek/esa/analyser/source/SourceAnalyser.java index f55650a..d26e4d7 100644 --- a/src/main/java/com/bartek/esa/analyser/source/SourceAnalyser.java +++ b/src/main/java/com/bartek/esa/analyser/source/SourceAnalyser.java @@ -1,6 +1,7 @@ package com.bartek.esa.analyser.source; import com.bartek.esa.analyser.core.Analyser; +import com.bartek.esa.context.constructor.ContextConstructor; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.error.EsaException; @@ -14,8 +15,8 @@ public class SourceAnalyser extends Analyser { private static final String JAVA_GLOB = "**/*.java"; private static final String LAYOUT_GLOB = "**/res/layout*/*.xml"; - public SourceAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider) { - super(pluginExecutor, plugins, fileProvider); + public SourceAnalyser(PluginExecutor pluginExecutor, Set plugins, FileProvider fileProvider, ContextConstructor contextConstructor) { + super(pluginExecutor, plugins, fileProvider, contextConstructor); } @Override diff --git a/src/main/java/com/bartek/esa/cli/model/enumeration/OutputType.java b/src/main/java/com/bartek/esa/cli/model/enumeration/OutputType.java new file mode 100644 index 0000000..a27e1c9 --- /dev/null +++ b/src/main/java/com/bartek/esa/cli/model/enumeration/OutputType.java @@ -0,0 +1,7 @@ +package com.bartek.esa.cli.model.enumeration; + +public enum OutputType { + DEFAULT, + COLOR, + JSON; +} diff --git a/src/main/java/com/bartek/esa/cli/model/CliArgsOptions.java b/src/main/java/com/bartek/esa/cli/model/object/CliArgsOptions.java similarity index 71% rename from src/main/java/com/bartek/esa/cli/model/CliArgsOptions.java rename to src/main/java/com/bartek/esa/cli/model/object/CliArgsOptions.java index a9ff835..9990e97 100644 --- a/src/main/java/com/bartek/esa/cli/model/CliArgsOptions.java +++ b/src/main/java/com/bartek/esa/cli/model/object/CliArgsOptions.java @@ -1,8 +1,10 @@ -package com.bartek.esa.cli.model; +package com.bartek.esa.cli.model.object; +import com.bartek.esa.cli.model.enumeration.OutputType; import lombok.Builder; import lombok.Data; +import java.io.File; import java.util.Set; import static java.util.Collections.emptySet; @@ -14,9 +16,11 @@ public class CliArgsOptions { private String apkAuditFile; private Set excludes; private Set plugins; - private boolean color; + private OutputType outputType; private Set severities; private boolean debug; + private File out; + private boolean strictMode; public boolean isSourceAnalysis() { return sourceAnalysisDirectory != null; @@ -30,6 +34,8 @@ public class CliArgsOptions { return CliArgsOptions.builder() .excludes(emptySet()) .plugins(emptySet()) + .severities(emptySet()) + .outputType(OutputType.DEFAULT) .build(); } } diff --git a/src/main/java/com/bartek/esa/cli/parser/CliArgsParser.java b/src/main/java/com/bartek/esa/cli/parser/CliArgsParser.java index e4a955c..ae7c240 100644 --- a/src/main/java/com/bartek/esa/cli/parser/CliArgsParser.java +++ b/src/main/java/com/bartek/esa/cli/parser/CliArgsParser.java @@ -1,11 +1,14 @@ package com.bartek.esa.cli.parser; -import com.bartek.esa.cli.model.CliArgsOptions; +import com.bartek.esa.cli.model.enumeration.OutputType; +import com.bartek.esa.cli.model.object.CliArgsOptions; import com.bartek.esa.cli.printer.PluginPrinter; import com.bartek.esa.core.model.enumeration.Severity; +import io.vavr.control.Try; import org.apache.commons.cli.*; import javax.inject.Inject; +import java.io.File; import java.util.HashSet; import java.util.stream.Collectors; @@ -19,10 +22,12 @@ public class CliArgsParser { private static final String EXCLUDE_OPT = "exclude"; private static final String HELP_OPT = "help"; private static final String PLUGINS_OPT = "plugins"; - private static final String COLOR_OPT = "color"; + private static final String FORMAT_OPT = "format"; + private static final String OUT_OPT = "out"; private static final String SEVERITIES_OPT = "severities"; private static final String DEBUG_OPT = "debug"; private static final String LIST_PLUGINS_OPT = "list-plugins"; + private static final String STRICT_OPT = "strict"; private final PluginPrinter pluginPrinter; @@ -66,12 +71,23 @@ public class CliArgsParser { .apkAuditFile(command.hasOption(APK_OPT) ? command.getOptionValue(APK_OPT) : null) .plugins(command.hasOption(PLUGINS_OPT) ? new HashSet<>(asList(command.getOptionValues(PLUGINS_OPT))) : emptySet()) .excludes(command.hasOption(EXCLUDE_OPT) ? new HashSet<>(asList(command.getOptionValues(EXCLUDE_OPT))) : emptySet()) - .color(command.hasOption(COLOR_OPT)) + .outputType(getOutputTypeForOptions(command)) .severities(command.hasOption(SEVERITIES_OPT) ? new HashSet<>(asList((command.getOptionValues(SEVERITIES_OPT)))) : stream(Severity.values()).map(Severity::name).collect(Collectors.toSet())) .debug(command.hasOption(DEBUG_OPT)) + .out(command.hasOption(OUT_OPT) ? new File(command.getOptionValue(OUT_OPT)) : null) + .strictMode(command.hasOption(STRICT_OPT)) .build(); } + private OutputType getOutputTypeForOptions(CommandLine command) { + if(command.hasOption(FORMAT_OPT)) { + return Try.of(() -> OutputType.valueOf(command.getOptionValue(FORMAT_OPT).toUpperCase())) + .getOrElse(OutputType.DEFAULT); + } + + return OutputType.DEFAULT; + } + private void printHelp() { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("esa", prepareOptions(), true); @@ -84,10 +100,12 @@ public class CliArgsParser { options.addOption(exclude()); options.addOption(plugins()); options.addOption(help()); - options.addOption(color()); + options.addOption(format()); options.addOption(severities()); options.addOption(debug()); options.addOption(listPlugins()); + options.addOption(out()); + options.addOption(strict()); return options; } @@ -134,10 +152,12 @@ public class CliArgsParser { .build(); } - private Option color() { + private Option format() { return Option.builder() - .longOpt(COLOR_OPT) - .desc("enable colored output") + .longOpt(FORMAT_OPT) + .argName("OUTPUT_TYPE") + .numberOfArgs(1) + .desc("select format format (available: default, color, json)") .build(); } @@ -147,7 +167,7 @@ public class CliArgsParser { .longOpt(SEVERITIES_OPT) .argName("SEVERITY") .numberOfArgs(Option.UNLIMITED_VALUES) - .desc("filter output to selected severities(available: " + severities + ")") + .desc("filter format to selected severities(available: " + severities + ")") .build(); } @@ -164,4 +184,20 @@ public class CliArgsParser { .desc("list available plugins") .build(); } + + private Option out() { + return Option.builder() + .longOpt(OUT_OPT) + .argName("PATH") + .numberOfArgs(1) + .desc("optional output of analysis - recommended to use with non-text output format") + .build(); + } + + private Option strict() { + return Option.builder() + .longOpt(STRICT_OPT) + .desc("enable strict mode - return code depends on analysis result (recommended to use in batch mode)") + .build(); + } } diff --git a/src/main/java/com/bartek/esa/context/constructor/ContextConstructor.java b/src/main/java/com/bartek/esa/context/constructor/ContextConstructor.java new file mode 100644 index 0000000..a95f182 --- /dev/null +++ b/src/main/java/com/bartek/esa/context/constructor/ContextConstructor.java @@ -0,0 +1,108 @@ +package com.bartek.esa.context.constructor; + +import com.bartek.esa.context.model.Context; +import com.bartek.esa.context.model.Source; +import com.bartek.esa.core.xml.XmlHelper; +import com.bartek.esa.error.EsaException; +import com.bartek.esa.file.matcher.PackageNameMatcher; +import com.github.javaparser.ParseProblemException; +import com.github.javaparser.Problem; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.TokenRange; +import com.github.javaparser.ast.CompilationUnit; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.inject.Inject; +import javax.xml.xpath.XPathConstants; +import java.io.File; +import java.util.Optional; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; + +public class ContextConstructor { + private final XmlHelper xmlHelper; + private final PackageNameMatcher packageNameMatcher; + + @Inject + public ContextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + this.xmlHelper = xmlHelper; + this.packageNameMatcher = packageNameMatcher; + } + + public Context construct(File androidManifestFile, Set javaFiles, Set layoutFiles) { + Document manifest = xmlHelper.parseXml(androidManifestFile); + String packageName = getPackageName(manifest); + + return Context.builder() + .packageName(packageName) + .minSdkVersion(getUsesSdkVersion(manifest, "android:minSdkVersion")) + .targetSdkVersion(getUsesSdkVersion(manifest, "android:targetSdkVersion")) + .maxSdkVersion(getUsesSdkVersion(manifest, "android:maxSdkVersion")) + .manifest(new Source<>(androidManifestFile, manifest)) + .javaSources(parseJavaFiles(javaFiles, packageName)) + .layouts(parseLayoutFiles(layoutFiles)) + .build(); + } + + private String getPackageName(Document androidManifest) { + return Optional.ofNullable(xmlHelper.xPath(androidManifest, "/manifest", XPathConstants.NODE)) + .map(n -> (Node) n) + .map(Node::getAttributes) + .map(attr -> attr.getNamedItem("package")) + .map(Node::getNodeValue) + .orElseThrow(() -> new EsaException("No 'package' attribute found in manifest file. Interrupting...")); + } + + private Integer getUsesSdkVersion(Document manifest, String attribute) { + return Optional.ofNullable(xmlHelper.xPath(manifest, "/manifest/uses-sdk", XPathConstants.NODE)) + .map(n -> (Node) n) + .map(Node::getAttributes) + .map(attr -> attr.getNamedItem(attribute)) + .map(Node::getNodeValue) + .map(Integer::parseInt) + .orElse(null); + + } + + private Set> parseLayoutFiles(Set layoutFiles) { + return layoutFiles.stream() + .map(file -> new Source<>(file, xmlHelper.parseXml(file))) + .collect(toSet()); + } + + private Set> parseJavaFiles(Set javaFiles, String packageName) { + return javaFiles.stream() + .filter(file -> packageNameMatcher.doesFileMatchPackageName(file, packageName)) + .map(file -> new Source<>(file, parseJava(file))) + .filter(s -> s.getModel() != null) + .collect(toSet()); + } + + private CompilationUnit parseJava(File javaFile) { + try { + return StaticJavaParser.parse(javaFile); + } catch (ParseProblemException e) { + printParsingErrorToStderr(e, javaFile); + } catch (Exception e) { + throw new EsaException(e); + } + + return null; + } + + private void printParsingErrorToStderr(ParseProblemException e, File file) { + e.getProblems().stream() + .map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getProblemRange(p), p.getMessage())) + .forEach(System.err::println); + } + + private String getProblemRange(Problem problem) { + return problem.getLocation() + .flatMap(TokenRange::toRange) + .map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column)) + .orElse(""); + } +} diff --git a/src/main/java/com/bartek/esa/context/di/ContextModule.java b/src/main/java/com/bartek/esa/context/di/ContextModule.java new file mode 100644 index 0000000..83e403c --- /dev/null +++ b/src/main/java/com/bartek/esa/context/di/ContextModule.java @@ -0,0 +1,15 @@ +package com.bartek.esa.context.di; + +import com.bartek.esa.context.constructor.ContextConstructor; +import com.bartek.esa.core.xml.XmlHelper; +import com.bartek.esa.file.matcher.PackageNameMatcher; +import dagger.Module; + +@Module +public class ContextModule { + + public ContextConstructor contextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + return new ContextConstructor(xmlHelper, packageNameMatcher); + } + +} diff --git a/src/main/java/com/bartek/esa/context/model/Context.java b/src/main/java/com/bartek/esa/context/model/Context.java new file mode 100644 index 0000000..2cbc21c --- /dev/null +++ b/src/main/java/com/bartek/esa/context/model/Context.java @@ -0,0 +1,25 @@ +package com.bartek.esa.context.model; + +import com.github.javaparser.ast.CompilationUnit; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.w3c.dom.Document; + +import java.util.Set; + + +@Getter +@Builder +@ToString +@EqualsAndHashCode +public class Context { + private String packageName; + private Integer minSdkVersion; + private Integer targetSdkVersion; + private Integer maxSdkVersion; + private Source manifest; + private Set> javaSources; + private Set> layouts; +} diff --git a/src/main/java/com/bartek/esa/context/model/Source.java b/src/main/java/com/bartek/esa/context/model/Source.java new file mode 100644 index 0000000..73c2e5b --- /dev/null +++ b/src/main/java/com/bartek/esa/context/model/Source.java @@ -0,0 +1,17 @@ +package com.bartek.esa.context.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.io.File; + +@Getter +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +public class Source { + private final File file; + private final M model; +} diff --git a/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java b/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java index b01718e..0091856 100644 --- a/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java @@ -1,20 +1,15 @@ package com.bartek.esa.core.archetype; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; +import com.bartek.esa.context.model.Context; +import com.bartek.esa.context.model.Source; +import org.w3c.dom.Document; -import java.io.File; - -public abstract class AndroidManifestPlugin extends XmlPlugin { - private final GlobMatcher globMatcher; - - public AndroidManifestPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - this.globMatcher = globMatcher; - } +public abstract class AndroidManifestPlugin extends BasePlugin { @Override - public boolean supports(File file) { - return globMatcher.fileMatchesGlobPattern(file, "**/AndroidManifest.xml"); + protected void run(Context context) { + run(context.getManifest()); } + + protected abstract void run(Source manifest); } diff --git a/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java b/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java index ed3465d..944e646 100644 --- a/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java @@ -1,49 +1,92 @@ package com.bartek.esa.core.archetype; +import com.bartek.esa.context.model.Context; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.model.object.Issue; -import org.w3c.dom.Document; +import com.github.javaparser.ast.expr.Expression; +import org.w3c.dom.Node; import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; + +import static java.lang.String.format; public abstract class BasePlugin implements Plugin { - private Set issues = new HashSet<>(); - private Document manifest; - private File file; + private Set issues; @Override - public void update(File file, Document manifest) { - this.file = file; - this.manifest = manifest; - this.issues.clear(); - } - - @Override - public Set runForIssues() { - run(file); + public Set runForIssues(Context context) { + issues = new HashSet<>(); + run(context); return issues; } - protected abstract void run(File file); + protected abstract void run(Context context); - protected void addIssue(Severity severity, Integer lineNumber, String line) { - addIssue(severity, "", lineNumber, line); + protected void addJavaIssue(Severity severity, File file, Expression expression) { + addIssue(severity, file, getLineNumberFromExpression(expression), expression.toString()); } - protected void addIssue(Severity severity, Map descriptionModel, Integer lineNumber, String line) { - addIssue(severity, "", descriptionModel, lineNumber, line); + private Integer getLineNumberFromExpression(Expression expression) { + return expression.getRange().map(r -> r.begin.line).orElse(null); + } + + protected void addJavaIssue(Severity severity, Map descriptionModel, File file, Expression expression) { + addIssue(severity, "", descriptionModel, file, getLineNumberFromExpression(expression), expression.toString()); } - protected void addIssue(Severity severity, String descriptionCode, Integer lineNumber, String line) { - addIssue(severity, descriptionCode, new HashMap<>(), lineNumber, line); + protected void addJavaIssue(Severity severity, String descriptionCode, File file, Expression expression) { + addIssue(severity, descriptionCode, new HashMap<>(), file, getLineNumberFromExpression(expression), expression.toString()); } - protected void addIssue(Severity severity, String descriptionCode, Map descriptionModel, Integer lineNumber, String line) { + protected void addJavaIssue(Severity severity, String descriptionCode, Map descriptionModel, File file, Expression expression) { + addIssue(severity, descriptionCode, descriptionModel, file, getLineNumberFromExpression(expression), expression.toString()); + } + + protected void addXmlIssue(Severity severity, File file, Node node) { + addIssue(severity, file, null, tagString(node)); + } + + protected void addXmlIssue(Severity severity, Map descriptionModel, File file, Node node) { + addIssue(severity, descriptionModel, file, null, tagString(node)); + } + + protected void addXmlIssue(Severity severity, String descriptionCode, File file, Node node) { + addIssue(severity, descriptionCode, file, null, tagString(node)); + } + + protected void addXmlIssue(Severity severity, String descriptionCode, Map descriptionModel, File file, Node node) { + addIssue(severity, descriptionCode, descriptionModel, file, null, tagString(node)); + } + + protected String tagString(Node node) { + Node[] attributes = new Node[node.getAttributes().getLength()]; + for (int i = 0; i < attributes.length; ++i) { + attributes[i] = node.getAttributes().item(i); + } + + String attributesString = Arrays.stream(attributes) + .map(n -> format("%s=\"%s\"", n.getNodeName(), n.getNodeValue())) + .collect(Collectors.joining(" ")); + + return format("<%s %s ...", node.getNodeName(), attributesString); + } + + protected void addIssue(Severity severity, File file, Integer lineNumber, String line) { + addIssue(severity, "", file, lineNumber, line); + } + + protected void addIssue(Severity severity, Map descriptionModel, File file, Integer lineNumber, String line) { + addIssue(severity, "", descriptionModel, file, lineNumber, line); + } + + protected void addIssue(Severity severity, String descriptionCode, File file, Integer lineNumber, String line) { + addIssue(severity, descriptionCode, new HashMap<>(), file, lineNumber, line); + } + + protected void addIssue(Severity severity, String descriptionCode, Map descriptionModel, File file, Integer lineNumber, String line) { Issue issue = Issue.builder() .severity(severity) .issuer(this.getClass()) @@ -54,18 +97,10 @@ public abstract class BasePlugin implements Plugin { .line(line) .build(); - issues.add(issue); + addIssue(issue); } protected void addIssue(Issue issue) { issues.add(issue); } - - protected File getOriginalFile() { - return file; - } - - protected Document getManifest() { - return manifest; - } } diff --git a/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java b/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java index f7f31a4..72c35f6 100644 --- a/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java @@ -1,92 +1,15 @@ package com.bartek.esa.core.archetype; -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.file.matcher.GlobMatcher; -import com.github.javaparser.ParseProblemException; -import com.github.javaparser.Problem; -import com.github.javaparser.StaticJavaParser; -import com.github.javaparser.TokenRange; +import com.bartek.esa.context.model.Context; +import com.bartek.esa.context.model.Source; import com.github.javaparser.ast.CompilationUnit; -import com.github.javaparser.ast.expr.Expression; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import javax.xml.xpath.XPathConstants; -import java.io.File; -import java.util.HashMap; - -import static java.lang.String.format; public abstract class JavaPlugin extends BasePlugin { - private final GlobMatcher globMatcher; - private final XmlHelper xmlHelper; - - public JavaPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - this.globMatcher = globMatcher; - this.xmlHelper = xmlHelper; - } @Override - public boolean supports(File file) { - return globMatcher.fileMatchesGlobPattern(file, "**/*.java"); + protected void run(Context context) { + context.getJavaSources().forEach(this::run); } - @Override - protected void run(File file) { - if(!isApplicationPackageFile(file)) { - return; - } - - try { - CompilationUnit compilationUnit = StaticJavaParser.parse(file); - run(compilationUnit); - } catch (ParseProblemException e) { - printParsingErrorToStderr(e, file); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void printParsingErrorToStderr(ParseProblemException e, File file) { - e.getProblems().stream() - .map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getRange(p), p.getMessage())) - .forEach(System.err::println); - } - - private String getRange(Problem problem) { - return problem.getLocation() - .flatMap(TokenRange::toRange) - .map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column)) - .orElse(""); - } - - private boolean isApplicationPackageFile(File file) { - Document manifest = getManifest(); - Node root = (Node) xmlHelper.xPath(manifest, "/manifest", XPathConstants.NODE); - Node packageValue = root.getAttributes().getNamedItem("package"); - - if(packageValue == null) { - Issue issue = Issue.builder() - .issuer(JavaPlugin.class) - .descriptionCode(".NO_PACKAGE") - .descriptionModel(new HashMap<>()) - .severity(Severity.ERROR) - .build(); - - addIssue(issue); - - return false; - } - - String path = packageValue.getNodeValue().replaceAll("\\.", "/"); - return globMatcher.fileMatchesGlobPattern(file, format("**/%s/**", path)); - } - - protected Integer getLineNumberFromExpression(Expression expression) { - return expression.getRange().map(r -> r.begin.line).orElse(null); - } - - public abstract void run(CompilationUnit compilationUnit); + protected abstract void run(Source compilationUnit); } diff --git a/src/main/java/com/bartek/esa/core/archetype/Plugin.java b/src/main/java/com/bartek/esa/core/archetype/Plugin.java index ffbd990..5de9758 100644 --- a/src/main/java/com/bartek/esa/core/archetype/Plugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/Plugin.java @@ -1,13 +1,10 @@ package com.bartek.esa.core.archetype; +import com.bartek.esa.context.model.Context; import com.bartek.esa.core.model.object.Issue; -import org.w3c.dom.Document; -import java.io.File; import java.util.Set; public interface Plugin { - boolean supports(File file); - void update(File file, Document manifest); - Set runForIssues(); + Set runForIssues(Context context); } diff --git a/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java b/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java index aa924e7..43d8c03 100644 --- a/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java @@ -1,20 +1,15 @@ package com.bartek.esa.core.archetype; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; +import com.bartek.esa.context.model.Context; +import com.bartek.esa.context.model.Source; +import org.w3c.dom.Document; -import java.io.File; - -public abstract class ResourceLayoutPlugin extends XmlPlugin { - private final GlobMatcher globMatcher; - - public ResourceLayoutPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - this.globMatcher = globMatcher; - } +public abstract class ResourceLayoutPlugin extends BasePlugin { @Override - public boolean supports(File file) { - return globMatcher.fileMatchesGlobPattern(file, "**/res/layout*/*.xml"); + protected void run(Context context) { + context.getLayouts().forEach(this::run); } + + protected abstract void run(Source layout); } diff --git a/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java b/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java deleted file mode 100644 index 19b41a0..0000000 --- a/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.bartek.esa.core.archetype; - -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import javax.xml.namespace.QName; -import java.io.File; -import java.util.Arrays; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.lang.String.format; - -public abstract class XmlPlugin extends BasePlugin { - private final GlobMatcher globMatcher; - private final XmlHelper xmlHelper; - - public XmlPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - this.globMatcher = globMatcher; - this.xmlHelper = xmlHelper; - } - - @Override - public boolean supports(File file) { - return globMatcher.fileMatchesGlobPattern(file, "**/*.xml"); - } - - @Override - protected void run(File file) { - Document xml = xmlHelper.parseXml(file); - run(xml); - } - - protected abstract void run(Document xml); - - protected Object xPath(Document xml, String expression, QName returnType) { - return xmlHelper.xPath(xml, expression, returnType); - } - - protected Stream stream(NodeList nodeList) { - return xmlHelper.stream(nodeList); - } - - protected String tagString(Node node) { - Node[] attributes = new Node[node.getAttributes().getLength()]; - for(int i=0; i format("%s=\"%s\"", n.getNodeName(), n.getNodeValue())) - .collect(Collectors.joining(" ")); - - return format("<%s %s ...", node.getNodeName(), attributesString); - } -} diff --git a/src/main/java/com/bartek/esa/core/di/CoreModule.java b/src/main/java/com/bartek/esa/core/di/CoreModule.java index 8a9d141..11285a2 100644 --- a/src/main/java/com/bartek/esa/core/di/CoreModule.java +++ b/src/main/java/com/bartek/esa/core/di/CoreModule.java @@ -4,6 +4,7 @@ import com.bartek.esa.core.desc.provider.DescriptionProvider; import com.bartek.esa.core.executor.PluginExecutor; import com.bartek.esa.core.helper.ParentNodeFinder; import com.bartek.esa.core.helper.StaticScopeHelper; +import com.bartek.esa.core.helper.StringConcatenationChecker; import com.bartek.esa.core.java.JavaSyntaxRegexProvider; import com.bartek.esa.core.xml.XmlHelper; import dagger.Module; @@ -13,8 +14,8 @@ import dagger.Provides; public class CoreModule { @Provides - public PluginExecutor pluginExecutor(XmlHelper xmlHelper) { - return new PluginExecutor(xmlHelper); + public PluginExecutor pluginExecutor() { + return new PluginExecutor(); } @Provides @@ -37,6 +38,11 @@ public class CoreModule { return new StaticScopeHelper(); } + @Provides + public StringConcatenationChecker stringConcatenationChecker(StaticScopeHelper staticScopeHelper) { + return new StringConcatenationChecker(staticScopeHelper); + } + @Provides public ParentNodeFinder parentNodeFinder() { return new ParentNodeFinder(); diff --git a/src/main/java/com/bartek/esa/core/di/PluginModule.java b/src/main/java/com/bartek/esa/core/di/PluginModule.java index b218fbc..e6f09ae 100644 --- a/src/main/java/com/bartek/esa/core/di/PluginModule.java +++ b/src/main/java/com/bartek/esa/core/di/PluginModule.java @@ -3,10 +3,11 @@ package com.bartek.esa.core.di; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.helper.ParentNodeFinder; import com.bartek.esa.core.helper.StaticScopeHelper; +import com.bartek.esa.core.helper.StringConcatenationChecker; import com.bartek.esa.core.java.JavaSyntaxRegexProvider; import com.bartek.esa.core.plugin.*; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; +import com.bartek.esa.file.matcher.PackageNameMatcher; import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; @@ -26,127 +27,127 @@ public class PluginModule { @Provides @IntoSet - public Plugin loggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - return new LoggingPlugin(globMatcher, xmlHelper, staticScopeHelper); + public Plugin loggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) { + return new LoggingPlugin(staticScopeHelper, stringConcatenationChecker); } @Provides @IntoSet - public Plugin debuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new DebuggablePlugin(globMatcher, xmlHelper); + public Plugin debuggablePlugin(XmlHelper xmlHelper) { + return new DebuggablePlugin(xmlHelper); } @Provides @IntoSet - public Plugin allowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new AllowBackupPlugin(globMatcher, xmlHelper); + public Plugin allowBackupPlugin(XmlHelper xmlHelper) { + return new AllowBackupPlugin(xmlHelper); } @Provides @IntoSet - public Plugin permissionRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new PermissionsRaceConditionPlugin(globMatcher, xmlHelper); + public Plugin permissionRaceConditionPlugin(XmlHelper xmlHelper) { + return new PermissionsRaceConditionPlugin(xmlHelper); } @Provides @IntoSet - public Plugin secureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new SecureRandomPlugin(globMatcher, xmlHelper); + public Plugin secureRandomPlugin() { + return new SecureRandomPlugin(); } @Provides @IntoSet - public Plugin implicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) { - return new ImplicitIntentsPlugin(globMatcher, xmlHelper, javaSyntaxRegexProvider); + public Plugin implicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) { + return new ImplicitIntentsPlugin(javaSyntaxRegexProvider); } @Provides @IntoSet - public Plugin sharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new SharedUidPlugin(globMatcher, xmlHelper); + public Plugin sharedUidPlugin(XmlHelper xmlHelper) { + return new SharedUidPlugin(xmlHelper); } @Provides @IntoSet - public Plugin usesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new UsesSdkPlugin(globMatcher, xmlHelper); + public Plugin usesSdkPlugin(XmlHelper xmlHelper) { + return new UsesSdkPlugin(xmlHelper); } @Provides @IntoSet - public Plugin cipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - return new CipherInstancePlugin(globMatcher, xmlHelper, staticScopeHelper); + public Plugin cipherInstancePlugin(StaticScopeHelper staticScopeHelper) { + return new CipherInstancePlugin(staticScopeHelper); } @Provides @IntoSet - public Plugin strictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - return new StrictModePlugin(globMatcher, xmlHelper, staticScopeHelper); + public Plugin strictModePlugin(StaticScopeHelper staticScopeHelper) { + return new StrictModePlugin(staticScopeHelper); } @Provides @IntoSet - public Plugin externalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) { - return new ExternalStoragePlugin(globMatcher, xmlHelper, parentNodeFinder); + public Plugin externalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) { + return new ExternalStoragePlugin(parentNodeFinder, staticScopeHelper); } @Provides @IntoSet - public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new SuppressWarningsPlugin(globMatcher, xmlHelper); + public Plugin suppressWarningsPlugin() { + return new SuppressWarningsPlugin(); } @Provides @IntoSet - public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new ExportedComponentsPlugin(globMatcher, xmlHelper); + public Plugin exportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + return new ExportedComponentsPlugin(xmlHelper, packageNameMatcher); } @Provides @IntoSet - public Plugin dangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new DangerousPermissionPlugin(globMatcher, xmlHelper); + public Plugin dangerousPermissionPlugin(XmlHelper xmlHelper) { + return new DangerousPermissionPlugin(xmlHelper); } @Provides @IntoSet - public Plugin textInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new TextInputValidationPlugin(globMatcher, xmlHelper); + public Plugin textInputValidationPlugin(XmlHelper xmlHelper) { + return new TextInputValidationPlugin(xmlHelper); } @Provides @IntoSet - public Plugin intentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new IntentFilterPlugin(globMatcher, xmlHelper); + public Plugin intentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + return new IntentFilterPlugin(xmlHelper, packageNameMatcher); } @Provides @IntoSet - public Plugin sqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new SqlInjectionPlugin(globMatcher, xmlHelper); + public Plugin sqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) { + return new SqlInjectionPlugin( stringConcatenationChecker); } @Provides @IntoSet - public Plugin worldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new WorldAccessPermissionsPlugin(globMatcher, xmlHelper); + public Plugin worldAccessPermissionsPlugin() { + return new WorldAccessPermissionsPlugin(); } @Provides @IntoSet - public Plugin orderedAndStickyBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new OrderedBroadcastPlugin(globMatcher, xmlHelper); + public Plugin orderedAndStickyBroadcastPlugin() { + return new OrderedBroadcastPlugin(); } @Provides @IntoSet - public Plugin webViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new WebViewPlugin(globMatcher, xmlHelper); + public Plugin webViewPlugin() { + return new WebViewPlugin(); } @Provides @IntoSet - public Plugin telephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - return new TelephonyManagerPlugin(globMatcher, xmlHelper); + public Plugin telephonyManagerPlugin() { + return new TelephonyManagerPlugin(); } } diff --git a/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java b/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java index d0cd00a..1cc1509 100644 --- a/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java +++ b/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java @@ -1,40 +1,30 @@ package com.bartek.esa.core.executor; +import com.bartek.esa.context.model.Context; import com.bartek.esa.core.archetype.Plugin; import com.bartek.esa.core.model.object.Issue; -import com.bartek.esa.core.xml.XmlHelper; -import org.w3c.dom.Document; -import javax.inject.Inject; -import java.io.File; import java.util.Set; +import java.util.function.Consumer; import static java.util.stream.Collectors.toSet; public class PluginExecutor { - private final XmlHelper xmlHelper; - @Inject - public PluginExecutor(XmlHelper xmlHelper) { - this.xmlHelper = xmlHelper; - } - - public Set executeForFiles(File manifest, Set files, Set plugins, boolean debug) { - return files.stream() - .peek(file -> { if(debug) System.out.printf("File: %s\n", file.getAbsolutePath()); }) - .map(file -> executeForFile(manifest, file, plugins, debug)) - .flatMap(Set::stream) - .collect(toSet()); - } - - private Set executeForFile(File manifest, File file, Set plugins, boolean debug) { - Document xmlManifest = xmlHelper.parseXml(manifest); + public Set executeForContext(Context context, Set plugins, boolean debug) { return plugins.stream() - .peek(plugin -> { if(debug) System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName()); }) - .peek(plugin -> plugin.update(file, xmlManifest)) - .filter(plugin -> plugin.supports(file)) - .map(Plugin::runForIssues) + .peek(logPlugin(debug)) + .map(plugin -> plugin.runForIssues(context)) .flatMap(Set::stream) .collect(toSet()); + + } + + private Consumer logPlugin(boolean debug) { + return plugin -> { + if(debug) { + System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName()); + } + }; } } diff --git a/src/main/java/com/bartek/esa/core/helper/NodeUtil.java b/src/main/java/com/bartek/esa/core/helper/NodeUtil.java new file mode 100644 index 0000000..d5accbe --- /dev/null +++ b/src/main/java/com/bartek/esa/core/helper/NodeUtil.java @@ -0,0 +1,39 @@ +package com.bartek.esa.core.helper; + +import com.github.javaparser.Position; +import com.github.javaparser.ast.Node; + +import static java.lang.String.format; + +public class NodeUtil { + private Node first; + + private NodeUtil(Node node) { + this.first = node; + } + + public static NodeUtil is(Node node) { + return new NodeUtil(node); + } + + public boolean after(Node second) { + Position firstPosition = getPosition(first); + Position secondPosition = getPosition(second); + return firstPosition.isAfter(secondPosition); + } + + private Position getPosition(Node node) { + return node.getRange() + .map(r -> r.begin) + .orElseGet(() -> { + System.err.println(format("Cannot determine position of:\n%s\nProduced results might not be reliable.")); + return new Position(0, 0); + }); + } + + public boolean before(Node second) { + Position firstPosition = getPosition(first); + Position secondPosition = getPosition(second); + return firstPosition.isBefore(secondPosition); + } +} diff --git a/src/main/java/com/bartek/esa/core/helper/StaticScopeHelper.java b/src/main/java/com/bartek/esa/core/helper/StaticScopeHelper.java index 67c0951..e430fed 100644 --- a/src/main/java/com/bartek/esa/core/helper/StaticScopeHelper.java +++ b/src/main/java/com/bartek/esa/core/helper/StaticScopeHelper.java @@ -20,6 +20,12 @@ public class StaticScopeHelper { public Predicate isFromScope(CompilationUnit compilationUnit, String methodName, String scope, String importScope) { return expr -> { + if(!expr.getName().getIdentifier().matches(methodName)) { + return false; + } + + // is called with scope + // Class.method() boolean isFromScope = expr.getScope() .filter(Expression::isNameExpr) .map(Expression::asNameExpr) @@ -28,6 +34,9 @@ public class StaticScopeHelper { .map(s -> s.equals(scope)) .orElse(false); + // is from static import + // import static a.b.Class.method + // method() if(!isFromScope) { isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream() .filter(ImportDeclaration::isStatic) @@ -39,6 +48,9 @@ public class StaticScopeHelper { .anyMatch(q -> q.equals(format("%s.%s", importScope, scope))); } + // is from import + // import static a.b.Class.* + // method() if(!isFromScope) { isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream() .filter(ImportDeclaration::isStatic) diff --git a/src/main/java/com/bartek/esa/core/helper/StringConcatenationChecker.java b/src/main/java/com/bartek/esa/core/helper/StringConcatenationChecker.java new file mode 100644 index 0000000..89c0bff --- /dev/null +++ b/src/main/java/com/bartek/esa/core/helper/StringConcatenationChecker.java @@ -0,0 +1,45 @@ +package com.bartek.esa.core.helper; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.MethodCallExpr; + +import javax.inject.Inject; +import java.util.function.Predicate; + +public class StringConcatenationChecker { + private final StaticScopeHelper staticScopeHelper; + + @Inject + public StringConcatenationChecker(StaticScopeHelper staticScopeHelper) { + + this.staticScopeHelper = staticScopeHelper; + } + + public boolean isStringConcatenation(CompilationUnit unit, Expression expr) { + Predicate isStringFormatMethod = staticScopeHelper.isFromScope(unit, "format", "String", "java.lang"); + if(expr.isMethodCallExpr() && isStringFormatMethod.test(expr.asMethodCallExpr())) { + return true; + } + + return isStringConcatenation(expr); + } + + private boolean isStringConcatenation(Expression expr) { + if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) { + return isLiteralStringOrConcatenation(expr); + } + + return false; + } + + private boolean isLiteralStringOrConcatenation(Expression expr) { + if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) { + boolean isLeftArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getLeft()); + boolean isRightArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getRight()); + return isLeftArgumentString || isRightArgumentString; + } + + return expr.isStringLiteralExpr(); + } +} diff --git a/src/main/java/com/bartek/esa/core/model/object/Issue.java b/src/main/java/com/bartek/esa/core/model/object/Issue.java index 355dfcb..9a402c1 100644 --- a/src/main/java/com/bartek/esa/core/model/object/Issue.java +++ b/src/main/java/com/bartek/esa/core/model/object/Issue.java @@ -23,7 +23,6 @@ public class Issue implements Comparable { public int compareTo(Object o) { Issue another = (Issue) o; int compByFile = file.compareTo(another.file); - if(compByFile != 0) { return compByFile; } diff --git a/src/main/java/com/bartek/esa/core/plugin/AllowBackupPlugin.java b/src/main/java/com/bartek/esa/core/plugin/AllowBackupPlugin.java index 87c21a6..f66233c 100644 --- a/src/main/java/com/bartek/esa/core/plugin/AllowBackupPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/AllowBackupPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants; import java.util.Optional; public class AllowBackupPlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public AllowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public AllowBackupPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE); + protected void run(Source manifest) { + Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/application", XPathConstants.NODE); Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:allowBackup")).ifPresentOrElse(n -> { if (!n.getNodeValue().equals("false")) { - addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString()); + addIssue(Severity.WARNING, ".NO_FALSE", manifest.getFile(), null, n.toString()); } - }, () -> addIssue(Severity.ERROR, ".NO_ATTR", null, null)); + }, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/CipherInstancePlugin.java b/src/main/java/com/bartek/esa/core/plugin/CipherInstancePlugin.java index 4567ced..0c35664 100644 --- a/src/main/java/com/bartek/esa/core/plugin/CipherInstancePlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/CipherInstancePlugin.java @@ -1,10 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.helper.StaticScopeHelper; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.MethodCallExpr; @@ -16,19 +15,18 @@ public class CipherInstancePlugin extends JavaPlugin { private final StaticScopeHelper staticScopeHelper; @Inject - public CipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - super(globMatcher, xmlHelper); + public CipherInstancePlugin(StaticScopeHelper staticScopeHelper) { this.staticScopeHelper = staticScopeHelper; } @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().equals("getInstance")) - .filter(staticScopeHelper.isFromScope(compilationUnit, "getInstance", "Cipher", "javax.crypto")) + .filter(staticScopeHelper.isFromScope(java.getModel(), "getInstance", "Cipher", "javax.crypto")) .filter(expr -> expr.getArguments().isNonEmpty()) .filter(expr -> !isFullCipherQualifier(expr.getArguments().get(0).toString())) - .forEach(expr -> addIssue(Severity.ERROR, getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.ERROR, java.getFile(), expr)); } private boolean isFullCipherQualifier(String qualifier) { diff --git a/src/main/java/com/bartek/esa/core/plugin/DangerousPermissionPlugin.java b/src/main/java/com/bartek/esa/core/plugin/DangerousPermissionPlugin.java index 381a09b..dbc5573 100644 --- a/src/main/java/com/bartek/esa/core/plugin/DangerousPermissionPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/DangerousPermissionPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -13,19 +13,20 @@ import javax.xml.xpath.XPathConstants; import java.util.Optional; public class DangerousPermissionPlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public DangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public DangerousPermissionPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - NodeList customPermissions = (NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET); - stream(customPermissions) + protected void run(Source manifest) { + NodeList customPermissions = (NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET); + xmlHelper.stream(customPermissions) .filter(this::isDangerousPermission) .filter(this::doesNotHaveDescription) - .forEach(permission -> addIssue(Severity.WARNING, null, tagString(permission))); + .forEach(permission -> addXmlIssue(Severity.WARNING, manifest.getFile(), permission)); } private boolean isDangerousPermission(Node permission) { diff --git a/src/main/java/com/bartek/esa/core/plugin/DebuggablePlugin.java b/src/main/java/com/bartek/esa/core/plugin/DebuggablePlugin.java index e198352..e436b10 100644 --- a/src/main/java/com/bartek/esa/core/plugin/DebuggablePlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/DebuggablePlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants; import java.util.Optional; public class DebuggablePlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public DebuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public DebuggablePlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE); + protected void run(Source manifest) { + Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/application", XPathConstants.NODE); Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:debuggable")).ifPresentOrElse(n -> { if(!n.getNodeValue().equals("false")) { - addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString()); + addIssue(Severity.WARNING, ".NO_FALSE", manifest.getFile(),null, n.toString()); } - }, () -> addIssue(Severity.ERROR, ".NO_ATTR",null, null)); + }, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java b/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java index 01410fa..db15f47 100644 --- a/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java @@ -1,10 +1,11 @@ package com.bartek.esa.core.plugin; -import com.bartek.esa.core.archetype.AndroidManifestPlugin; +import com.bartek.esa.context.model.Context; +import com.bartek.esa.core.archetype.BasePlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; -import org.w3c.dom.Document; +import com.bartek.esa.file.matcher.PackageNameMatcher; +import com.github.javaparser.ast.expr.MethodCallExpr; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -15,27 +16,46 @@ import java.util.Optional; import static java.lang.String.format; -public class ExportedComponentsPlugin extends AndroidManifestPlugin { +public class ExportedComponentsPlugin extends BasePlugin { + private final XmlHelper xmlHelper; + private final PackageNameMatcher packageNameMatcher; @Inject - public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public ExportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + this.xmlHelper = xmlHelper; + this.packageNameMatcher = packageNameMatcher; } @Override - protected void run(Document xml) { - findExportedComponents(xml, "activity"); - findExportedComponents(xml, "service"); - findExportedComponents(xml, "receiver"); - findExportedProviders(xml); + protected void run(Context context) { + findExportedComponents(context, "activity"); + findExportedComponents(context, "service"); + findExportedComponents(context, "receiver"); + findExportedProviders(context); } - private void findExportedComponents(Document xml, String component) { - NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET); - stream(exportedActivities) + private void findExportedComponents(Context context, String component) { + NodeList exportedActivities = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), format("/manifest/application/%s", component), XPathConstants.NODESET); + xmlHelper.stream(exportedActivities) .filter(this::isExported) .filter(node -> doesNotHavePermission(node, "android:permission")) - .forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null)); + .forEach(node -> { + String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue(); + String canonicalName = context.getPackageName() + componentName; + if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) { + addIssue(Severity.WARNING, ".NO_PERMISSION.DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null); + } else { + addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null); + } + }); + } + + private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) { + return context.getJavaSources().stream() + .filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName)) + .flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream()) + .filter(expr -> expr.getName().getIdentifier().equals("getIntent")) + .anyMatch(expr -> expr.getArguments().isEmpty()); } private Map getModel(Node node) { @@ -45,13 +65,13 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin { ); } - private void findExportedProviders(Document xml) { - NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET); - stream(exportedProviders) + private void findExportedProviders(Context context) { + NodeList exportedProviders = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), "/manifest/application/provider", XPathConstants.NODESET); + xmlHelper.stream(exportedProviders) .filter(this::isExported) .filter(node -> doesNotHavePermission(node, "android:writePermission") || doesNotHavePermission(node, "android:readPermission")) - .forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null)); + .forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null)); } private boolean doesNotHavePermission(Node node, String permissionAttribute) { @@ -68,13 +88,4 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin { .map(v -> v.equals("true")) .orElse(false); } - - private String nodeToString(Node node) { - String nodeName = Optional.ofNullable(node.getAttributes().getNamedItem("android:name")) - .map(Node::getNodeValue) - .map(name -> format(" android:name=\"%s\"", name)) - .orElse(""); - - return format("<%s%s ...", node.getNodeName(), nodeName); - } } diff --git a/src/main/java/com/bartek/esa/core/plugin/ExternalStoragePlugin.java b/src/main/java/com/bartek/esa/core/plugin/ExternalStoragePlugin.java index 2241c37..0fb9b86 100644 --- a/src/main/java/com/bartek/esa/core/plugin/ExternalStoragePlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/ExternalStoragePlugin.java @@ -1,44 +1,51 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.helper.ParentNodeFinder; +import com.bartek.esa.core.helper.StaticScopeHelper; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.MethodCallExpr; import javax.inject.Inject; +import java.util.function.Consumer; + +import static com.bartek.esa.core.helper.NodeUtil.is; public class ExternalStoragePlugin extends JavaPlugin { private final ParentNodeFinder parentNodeFinder; + private final StaticScopeHelper staticScopeHelper; @Inject - public ExternalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) { - super(globMatcher, xmlHelper); + public ExternalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) { this.parentNodeFinder = parentNodeFinder; + this.staticScopeHelper = staticScopeHelper; } @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() - .filter(expr -> expr.getName().getIdentifier().matches("getExternalStorageDirectory|getExternalStoragePublicDirectory")) - .forEach(this::findCheckingStorageStateForAccessingExternalStorage); + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() + .filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageDirectory|getExternalStoragePublicDirectory", "Environment", "android.os")) + .forEach(findCheckingStorageStateForAccessingExternalStorage(java)); } - private void findCheckingStorageStateForAccessingExternalStorage(MethodCallExpr accessingToExternalStorage) { - parentNodeFinder.findParentNode(accessingToExternalStorage, MethodDeclaration.class).ifPresent(methodDeclaration -> - findCheckingStorageStateInMethodDeclaration(accessingToExternalStorage, methodDeclaration) - ); + private Consumer findCheckingStorageStateForAccessingExternalStorage(Source java) { + return accessingToExternalStorage -> parentNodeFinder + .findParentNode(accessingToExternalStorage, MethodDeclaration.class) + .ifPresent(methodDeclaration -> + findCheckingStorageStateInMethodDeclaration(java, accessingToExternalStorage, methodDeclaration) + ); } - private void findCheckingStorageStateInMethodDeclaration(MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) { + private void findCheckingStorageStateInMethodDeclaration(Source java, MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) { boolean isStateBeingChecked = methodDeclaration.findAll(MethodCallExpr.class).stream() - .anyMatch(e -> e.getName().getIdentifier().equals("getExternalStorageState")); + .filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageState", "Environment", "android.os")) + .anyMatch(checkingMethod -> is(accessingToExternalStorage).after(checkingMethod)); if (!isStateBeingChecked) { - addIssue(Severity.WARNING, getLineNumberFromExpression(accessingToExternalStorage), accessingToExternalStorage.toString()); + addJavaIssue(Severity.WARNING, java.getFile(), accessingToExternalStorage); } } } diff --git a/src/main/java/com/bartek/esa/core/plugin/ImplicitIntentsPlugin.java b/src/main/java/com/bartek/esa/core/plugin/ImplicitIntentsPlugin.java index 92b8f4d..abfccb5 100644 --- a/src/main/java/com/bartek/esa/core/plugin/ImplicitIntentsPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/ImplicitIntentsPlugin.java @@ -1,10 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.java.JavaSyntaxRegexProvider; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; @@ -17,27 +16,26 @@ import java.util.Optional; import java.util.stream.Collectors; public class ImplicitIntentsPlugin extends JavaPlugin { - private final JavaSyntaxRegexProvider java; + private final JavaSyntaxRegexProvider javaSyntax; @Inject - public ImplicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) { - super(globMatcher, xmlHelper); - this.java = javaSyntaxRegexProvider; + public ImplicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) { + this.javaSyntax = javaSyntaxRegexProvider; } @Override - public void run(CompilationUnit compilationUnit) { - checkCreatingImplicitIntents(compilationUnit); - checkCreatingPendingIntentsWithoutIntentVariable(compilationUnit); - checkCreatingPendingIntentsWithIntentVariables(compilationUnit); - checkCreatingPendingIntentsWithIntentsArraysVariables(compilationUnit); + public void run(Source java) { + checkCreatingImplicitIntents(java); + checkCreatingPendingIntentsWithoutIntentVariable(java); + checkCreatingPendingIntentsWithIntentVariables(java); + checkCreatingPendingIntentsWithIntentsArraysVariables(java); } // Works for: // Intent[] myIntents = { new Intent(...), ... } // getActivities(this, 0, myIntents, 0); - private void checkCreatingPendingIntentsWithIntentsArraysVariables(CompilationUnit compilationUnit) { - List implicitIntentsArraysVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream() + private void checkCreatingPendingIntentsWithIntentsArraysVariables(Source java) { + List implicitIntentsArraysVariables = java.getModel().findAll(ObjectCreationExpr.class).stream() .filter(expr -> expr.getType().getName().getIdentifier().equals("Intent")) .filter(this::isCreatingImplicitIntent) .map(Node::getParentNode) @@ -49,31 +47,31 @@ public class ImplicitIntentsPlugin extends JavaPlugin { .map(VariableDeclarator::getName) .map(SimpleName::getIdentifier) .collect(Collectors.toList()); - compilationUnit.findAll(MethodCallExpr.class).stream() + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("getActivities")) .filter(expr -> expr.getArguments().size() >= 4) .filter(expr -> expr.getArguments().get(2).isNameExpr()) .filter(expr -> implicitIntentsArraysVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier())) - .forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr)); } - private void checkCreatingImplicitIntents(CompilationUnit compilationUnit) { - List intentVariables = getIntentVariables(compilationUnit); - checkAllSetActionMethodInvocations(compilationUnit, intentVariables); - checkCreatingImplicitIntentsUsingConstructor(compilationUnit); + private void checkCreatingImplicitIntents(Source java) { + List intentVariables = getIntentVariables(java); + checkAllSetActionMethodInvocations(java, intentVariables); + checkCreatingImplicitIntentsUsingConstructor(java); } // Works for: new Intent(Intent.ABC), new Intent(ABC) - private void checkCreatingImplicitIntentsUsingConstructor(CompilationUnit compilationUnit) { - compilationUnit.findAll(ObjectCreationExpr.class).stream() + private void checkCreatingImplicitIntentsUsingConstructor(Source java) { + java.getModel().findAll(ObjectCreationExpr.class).stream() .filter(expr -> expr.getType().getName().getIdentifier().equals("Intent")) .filter(this::isCreatingImplicitIntent) - .forEach(objectCreation -> addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(objectCreation), objectCreation.toString())); + .forEach(objectCreation -> addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), objectCreation)); } // Returns: i for: Intent i = new Intent(...) - private List getIntentVariables(CompilationUnit compilationUnit) { - return compilationUnit.findAll(VariableDeclarationExpr.class).stream() + private List getIntentVariables(Source java) { + return java.getModel().findAll(VariableDeclarationExpr.class).stream() .filter(expr -> expr.getElementType().toString().equals("Intent")) .map(VariableDeclarationExpr::getVariables) .flatMap(NodeList::stream) @@ -90,7 +88,7 @@ public class ImplicitIntentsPlugin extends JavaPlugin { // Works for: new Intent(CONSTANT, ...) if (arguments.size() == 1) { Expression argument = arguments.get(0); - isImplicit = java.isConstant(argument); + isImplicit = javaSyntax.isConstant(argument); } // Not works for: new Intent(this, ...) @@ -107,14 +105,14 @@ public class ImplicitIntentsPlugin extends JavaPlugin { } // Works for: i.setAction(...) - private void checkAllSetActionMethodInvocations(CompilationUnit compilationUnit, List intentVariables) { - compilationUnit.findAll(MethodCallExpr.class).forEach(methodCall -> { + private void checkAllSetActionMethodInvocations(Source java, List intentVariables) { + java.getModel().findAll(MethodCallExpr.class).forEach(methodCall -> { boolean isCalledOnIntentObject = methodCall.getScope() .map(Expression::toString) .filter(intentVariables::contains) .isPresent(); if(isCalledOnIntentObject && methodCall.getName().getIdentifier().equals("setAction")) { - addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(methodCall), methodCall.toString()); + addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), methodCall); } }); } @@ -122,8 +120,8 @@ public class ImplicitIntentsPlugin extends JavaPlugin { // Works for: // Intent myIntent = new Intent(...) // getActivity(this, 0, myIntent, 0); - private void checkCreatingPendingIntentsWithIntentVariables(CompilationUnit compilationUnit) { - List implicitIntentsVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream() + private void checkCreatingPendingIntentsWithIntentVariables(Source java) { + List implicitIntentsVariables = java.getModel().findAll(ObjectCreationExpr.class).stream() .filter(expr -> expr.getType().getName().getIdentifier().equals("Intent")) .filter(this::isCreatingImplicitIntent) .map(Node::getParentNode) @@ -133,30 +131,30 @@ public class ImplicitIntentsPlugin extends JavaPlugin { .map(VariableDeclarator::getName) .map(SimpleName::getIdentifier) .collect(Collectors.toList()); - compilationUnit.findAll(MethodCallExpr.class).stream() + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService")) .filter(expr -> expr.getArguments().size() >= 4) .filter(expr -> expr.getArguments().get(2).isNameExpr()) .filter(expr -> implicitIntentsVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier())) - .forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr)); } - private void checkCreatingPendingIntentsWithoutIntentVariable(CompilationUnit compilationUnit) { + private void checkCreatingPendingIntentsWithoutIntentVariable(Source java) { // Works for: getActivity(this, 0, new Intent(...), 0) - compilationUnit.findAll(MethodCallExpr.class).stream() + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService")) .filter(expr -> expr.getArguments().size() >= 4) .filter(expr -> expr.getArguments().get(2).isObjectCreationExpr()) .filter(expr -> isCreatingImplicitIntent(expr.getArguments().get(2).asObjectCreationExpr())) - .forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr)); // Works for: getActivities(this, 0, new Intent[] { new Intent(...), ...}, 0) - compilationUnit.findAll(MethodCallExpr.class).stream() + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("getActivities")) .filter(expr -> expr.getArguments().size() >= 4) .filter(expr -> expr.getArguments().get(2).isArrayCreationExpr()) .filter(expr -> isCreatingImplicitIntentsArray(expr.getArguments().get(2).asArrayCreationExpr())) - .forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr)); } private boolean isCreatingImplicitIntentsArray(ArrayCreationExpr arrayCreationExpr) { diff --git a/src/main/java/com/bartek/esa/core/plugin/IntentFilterPlugin.java b/src/main/java/com/bartek/esa/core/plugin/IntentFilterPlugin.java index 3272674..68cfe9b 100644 --- a/src/main/java/com/bartek/esa/core/plugin/IntentFilterPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/IntentFilterPlugin.java @@ -1,30 +1,51 @@ package com.bartek.esa.core.plugin; -import com.bartek.esa.core.archetype.AndroidManifestPlugin; +import com.bartek.esa.context.model.Context; +import com.bartek.esa.core.archetype.BasePlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; -import org.w3c.dom.Document; +import com.bartek.esa.file.matcher.PackageNameMatcher; +import com.github.javaparser.ast.expr.MethodCallExpr; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.inject.Inject; import java.util.Map; +import java.util.Optional; -public class IntentFilterPlugin extends AndroidManifestPlugin { +public class IntentFilterPlugin extends BasePlugin { + private final XmlHelper xmlHelper; + private final PackageNameMatcher packageNameMatcher; @Inject - public IntentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public IntentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) { + this.xmlHelper = xmlHelper; + this.packageNameMatcher = packageNameMatcher; } @Override - protected void run(Document xml) { - NodeList filters = xml.getElementsByTagName("intent-filter"); - stream(filters) + protected void run(Context context) { + NodeList filters = context.getManifest().getModel().getElementsByTagName("intent-filter"); + xmlHelper.stream(filters) .filter(this::isNotMainActivity) + .filter(this::isNotExported) .map(Node::getParentNode) - .forEach(n -> addIssue(Severity.INFO, getModel(n), null, null)); + .forEach(node -> { + String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue(); + String canonicalName = context.getPackageName() + componentName; + if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) { + addIssue(Severity.WARNING, ".DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null); + } else { + addIssue(Severity.WARNING, getModel(node), context.getManifest().getFile(), null, null); + } + }); + } + + private boolean isNotExported(Node component) { + return !Optional.ofNullable(component.getAttributes().getNamedItem("android:exported")) + .map(Node::getNodeValue) + .map(Boolean::parseBoolean) + .orElse(false); } private Map getModel(Node node) { @@ -35,17 +56,25 @@ public class IntentFilterPlugin extends AndroidManifestPlugin { } private boolean isNotMainActivity(Node filter) { - long mainActivityIntentFilters = stream(filter.getChildNodes()) - .filter(n -> n.getNodeName().matches("action|category")) + long mainActivityIntentFilters = xmlHelper.stream(filter.getChildNodes()) + .filter(n -> n.getNodeName().matches("action|category|data")) .map(n -> n.getAttributes().getNamedItem("android:name")) .map(Node::getNodeValue) .filter(v -> v.equals("android.intent.action.MAIN") || v.equals("android.intent.category.LAUNCHER")) .count(); - long currentIntentFilters = stream(filter.getChildNodes()) - .filter(n -> n.getNodeName().matches("action|category")) + long currentIntentFilters = xmlHelper.stream(filter.getChildNodes()) + .filter(n -> n.getNodeName().matches("action|category|data")) .count(); return mainActivityIntentFilters != currentIntentFilters; } + + private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) { + return context.getJavaSources().stream() + .filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName)) + .flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream()) + .filter(expr -> expr.getName().getIdentifier().equals("getIntent")) + .anyMatch(expr -> expr.getArguments().isEmpty()); + } } diff --git a/src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java b/src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java index 5359fd1..133c468 100644 --- a/src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java @@ -1,10 +1,10 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.helper.StaticScopeHelper; +import com.bartek.esa.core.helper.StringConcatenationChecker; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.MethodCallExpr; @@ -12,18 +12,21 @@ import javax.inject.Inject; public class LoggingPlugin extends JavaPlugin { private final StaticScopeHelper staticScopeHelper; + private final StringConcatenationChecker stringConcatenationChecker; @Inject - public LoggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - super(globMatcher, xmlHelper); + public LoggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) { this.staticScopeHelper = staticScopeHelper; + this.stringConcatenationChecker = stringConcatenationChecker; } @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("v|d|i|w|e|wtf")) - .filter(staticScopeHelper.isFromScope(compilationUnit, "v|d|i|w|e|wtf", "Log", "android.util")) - .forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString())); + .filter(staticScopeHelper.isFromScope(java.getModel(), "v|d|i|w|e|wtf", "Log", "android.util")) + .filter(expr -> expr.getArguments().size() >= 2) + .filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(1))) + .forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/OrderedBroadcastPlugin.java b/src/main/java/com/bartek/esa/core/plugin/OrderedBroadcastPlugin.java index 89a91fd..71f41dc 100644 --- a/src/main/java/com/bartek/esa/core/plugin/OrderedBroadcastPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/OrderedBroadcastPlugin.java @@ -1,25 +1,17 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.MethodCallExpr; -import javax.inject.Inject; - public class OrderedBroadcastPlugin extends JavaPlugin { - @Inject - public OrderedBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("sendOrderedBroadcast|sendOrderedBroadcastAsUser|sendStickyOrderedBroadcast|sendStickyOrderedBroadcastAsUser")) - .forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/PermissionsRaceConditionPlugin.java b/src/main/java/com/bartek/esa/core/plugin/PermissionsRaceConditionPlugin.java index 138521c..e4c8b3e 100644 --- a/src/main/java/com/bartek/esa/core/plugin/PermissionsRaceConditionPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/PermissionsRaceConditionPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -15,21 +15,22 @@ import java.util.Map; import static java.lang.Integer.parseInt; public class PermissionsRaceConditionPlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public PermissionsRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public PermissionsRaceConditionPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - boolean isAnyPermissionDefined = ((NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET)).getLength() > 0; + protected void run(Source manifest) { + boolean isAnyPermissionDefined = ((NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET)).getLength() > 0; if(isAnyPermissionDefined) { - Node usesSdkNode = (Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE); + Node usesSdkNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest/uses-sdk", XPathConstants.NODE); Node minSdkVersionNode = usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion"); int minSdkVersion = parseInt(minSdkVersionNode.getNodeValue()); if(minSdkVersion < 21) { - addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), null, minSdkVersionNode.toString()); + addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), manifest.getFile(), null, minSdkVersionNode.toString()); } } } diff --git a/src/main/java/com/bartek/esa/core/plugin/SecureRandomPlugin.java b/src/main/java/com/bartek/esa/core/plugin/SecureRandomPlugin.java index 0587539..4dbf696 100644 --- a/src/main/java/com/bartek/esa/core/plugin/SecureRandomPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/SecureRandomPlugin.java @@ -1,26 +1,18 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.ObjectCreationExpr; -import javax.inject.Inject; - public class SecureRandomPlugin extends JavaPlugin { - @Inject - public SecureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(ObjectCreationExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(ObjectCreationExpr.class).stream() .filter(expr -> expr.getType().getName().getIdentifier().equals("SecureRandom")) .filter(expr -> expr.getArguments().isNonEmpty()) - .forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/SharedUidPlugin.java b/src/main/java/com/bartek/esa/core/plugin/SharedUidPlugin.java index b0754ad..97181bd 100644 --- a/src/main/java/com/bartek/esa/core/plugin/SharedUidPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/SharedUidPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -12,17 +12,18 @@ import javax.xml.xpath.XPathConstants; import java.util.Optional; public class SharedUidPlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public SharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public SharedUidPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - Node manifestNode = (Node) xPath(xml, "/manifest", XPathConstants.NODE); + protected void run(Source manifest) { + Node manifestNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest", XPathConstants.NODE); Optional.ofNullable(manifestNode.getAttributes().getNamedItem("android:sharedUserId")).ifPresent(node -> { - addIssue(Severity.VULNERABILITY, null, node.toString()); + addIssue(Severity.VULNERABILITY, manifest.getFile(), null, node.toString()); }); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/SqlInjectionPlugin.java b/src/main/java/com/bartek/esa/core/plugin/SqlInjectionPlugin.java index 2e9ab6b..9b94f37 100644 --- a/src/main/java/com/bartek/esa/core/plugin/SqlInjectionPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/SqlInjectionPlugin.java @@ -1,25 +1,28 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; +import com.bartek.esa.core.helper.StringConcatenationChecker; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.MethodCallExpr; import javax.inject.Inject; public class SqlInjectionPlugin extends JavaPlugin { + private final StringConcatenationChecker stringConcatenationChecker; @Inject - public SqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public SqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) { + this.stringConcatenationChecker = stringConcatenationChecker; } @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().equals("rawQuery")) - .forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString())); + .filter(expr -> expr.getArguments().size() >= 2) + .filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(0))) + .forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/StrictModePlugin.java b/src/main/java/com/bartek/esa/core/plugin/StrictModePlugin.java index 273f2af..89aa2cd 100644 --- a/src/main/java/com/bartek/esa/core/plugin/StrictModePlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/StrictModePlugin.java @@ -1,10 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.helper.StaticScopeHelper; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.MethodCallExpr; @@ -14,16 +13,15 @@ public class StrictModePlugin extends JavaPlugin { private final StaticScopeHelper staticScopeHelper; @Inject - public StrictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) { - super(globMatcher, xmlHelper); + public StrictModePlugin(StaticScopeHelper staticScopeHelper) { this.staticScopeHelper = staticScopeHelper; } @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().equals("setThreadPolicy")) - .filter(staticScopeHelper.isFromScope(compilationUnit, "setThreadPolicy", "StrictMode", "android.os")) - .forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString())); + .filter(staticScopeHelper.isFromScope(java.getModel(), "setThreadPolicy", "StrictMode", "android.os")) + .forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/SuppressWarningsPlugin.java b/src/main/java/com/bartek/esa/core/plugin/SuppressWarningsPlugin.java index ad4f7fc..e053743 100644 --- a/src/main/java/com/bartek/esa/core/plugin/SuppressWarningsPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/SuppressWarningsPlugin.java @@ -1,25 +1,17 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.AnnotationExpr; -import javax.inject.Inject; - public class SuppressWarningsPlugin extends JavaPlugin { - @Inject - public SuppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(AnnotationExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(AnnotationExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().equals("SuppressWarnings")) - .forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/TelephonyManagerPlugin.java b/src/main/java/com/bartek/esa/core/plugin/TelephonyManagerPlugin.java index 86b88a1..22e4128 100644 --- a/src/main/java/com/bartek/esa/core/plugin/TelephonyManagerPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/TelephonyManagerPlugin.java @@ -1,26 +1,18 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.CastExpr; -import javax.inject.Inject; - public class TelephonyManagerPlugin extends JavaPlugin { - @Inject - public TelephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(CastExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(CastExpr.class).stream() .filter(expr -> expr.getType().isClassOrInterfaceType()) .filter(expr -> expr.getType().asClassOrInterfaceType().getName().getIdentifier().equals("TelephonyManager")) - .forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/TextInputValidationPlugin.java b/src/main/java/com/bartek/esa/core/plugin/TextInputValidationPlugin.java index f9c5054..6337afd 100644 --- a/src/main/java/com/bartek/esa/core/plugin/TextInputValidationPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/TextInputValidationPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.ResourceLayoutPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -12,18 +12,19 @@ import javax.inject.Inject; import java.util.Optional; public class TextInputValidationPlugin extends ResourceLayoutPlugin { + private final XmlHelper xmlHelper; @Inject - public TextInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public TextInputValidationPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - NodeList editTextNodes = xml.getElementsByTagName("EditText"); - stream(editTextNodes) + protected void run(Source layout) { + NodeList editTextNodes = layout.getModel().getElementsByTagName("EditText"); + xmlHelper.stream(editTextNodes) .filter(this::doesNotHaveInputType) - .forEach(n -> addIssue(Severity.WARNING, null, tagString(n))); + .forEach(n -> addXmlIssue(Severity.WARNING, layout.getFile(), n)); } private boolean doesNotHaveInputType(Node editText) { diff --git a/src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java b/src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java index b65a4f2..e93a542 100644 --- a/src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java @@ -1,9 +1,9 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.AndroidManifestPlugin; import com.bartek.esa.core.model.enumeration.Severity; import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -13,22 +13,23 @@ import java.util.Map; import java.util.Optional; public class UsesSdkPlugin extends AndroidManifestPlugin { + private final XmlHelper xmlHelper; @Inject - public UsesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); + public UsesSdkPlugin(XmlHelper xmlHelper) { + this.xmlHelper = xmlHelper; } @Override - protected void run(Document xml) { - Optional.ofNullable((Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> { + protected void run(Source manifest) { + Optional.ofNullable((Node) xmlHelper.xPath(manifest.getModel(), "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> { if(usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion") == null) { - addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", null, null); + addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", manifest.getFile(), null, null); } Optional.ofNullable(usesSdkNode.getAttributes().getNamedItem("android:maxSdkVersion")).ifPresent(maxSdkVersion -> - addIssue(Severity.ERROR, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()),null, maxSdkVersion.toString()) + addIssue(Severity.ERROR, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()), manifest.getFile(), null, maxSdkVersion.toString()) ); - }, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", null, null)); + }, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", manifest.getFile(), null, null)); } } diff --git a/src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java b/src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java index 07de49b..8f336a0 100644 --- a/src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java @@ -1,51 +1,47 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; -import javax.inject.Inject; +import java.util.function.Consumer; public class WebViewPlugin extends JavaPlugin { private static final String SETTINGS_METHODS = "addJavascriptInterface|setJavaScriptEnabled|setWebContentsDebuggingEnabled|setAllowFileAccess|setDomStorageEnabled"; - @Inject - public WebViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(MethodCallExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(MethodCallExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches(SETTINGS_METHODS)) - .forEach(this::issueMethod); + .forEach(issueMethod(java)); } - private void issueMethod(MethodCallExpr methodCall) { - switch (methodCall.getName().getIdentifier()) { - case "addJavascriptInterface": - addIssue(Severity.VULNERABILITY, ".JS_INTERFACE", getLineNumberFromExpression(methodCall), methodCall.toString()); - break; - case "setJavaScriptEnabled": - issueSettingsMethod(methodCall, ".JS_ENABLED"); - break; - case "setWebContentsDebuggingEnabled": - issueSettingsMethod(methodCall, ".DEBUGGING_ENABLED"); - break; - case "setAllowFileAccess": - issueSettingsMethod(methodCall, ".ALLOW_FILE_ACCESS"); - break; - } + private Consumer issueMethod(Source java) { + return methodCall -> { + switch (methodCall.getName().getIdentifier()) { + case "addJavascriptInterface": + addJavaIssue(Severity.VULNERABILITY, ".JS_INTERFACE", java.getFile(), methodCall); + break; + case "setJavaScriptEnabled": + issueSettingsMethod(java, methodCall, ".JS_ENABLED"); + break; + case "setWebContentsDebuggingEnabled": + issueSettingsMethod(java, methodCall, ".DEBUGGING_ENABLED"); + break; + case "setAllowFileAccess": + issueSettingsMethod(java, methodCall, ".ALLOW_FILE_ACCESS"); + break; + } + }; } - private void issueSettingsMethod(MethodCallExpr methodCall, String descriptionCode) { + private void issueSettingsMethod(Source java, MethodCallExpr methodCall, String descriptionCode) { Expression firstArg = methodCall.getArguments().get(0); if (firstArg.isBooleanLiteralExpr() && firstArg.asBooleanLiteralExpr().getValue()) { - addIssue(Severity.WARNING, descriptionCode, getLineNumberFromExpression(methodCall), methodCall.toString()); + addJavaIssue(Severity.WARNING, descriptionCode, java.getFile(), methodCall); } } } diff --git a/src/main/java/com/bartek/esa/core/plugin/WorldAccessPermissionsPlugin.java b/src/main/java/com/bartek/esa/core/plugin/WorldAccessPermissionsPlugin.java index fb1c8d1..45d00d8 100644 --- a/src/main/java/com/bartek/esa/core/plugin/WorldAccessPermissionsPlugin.java +++ b/src/main/java/com/bartek/esa/core/plugin/WorldAccessPermissionsPlugin.java @@ -1,32 +1,25 @@ package com.bartek.esa.core.plugin; +import com.bartek.esa.context.model.Source; import com.bartek.esa.core.archetype.JavaPlugin; import com.bartek.esa.core.model.enumeration.Severity; -import com.bartek.esa.core.xml.XmlHelper; -import com.bartek.esa.file.matcher.GlobMatcher; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.NameExpr; -import javax.inject.Inject; import java.util.Map; public class WorldAccessPermissionsPlugin extends JavaPlugin { - @Inject - public WorldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { - super(globMatcher, xmlHelper); - } - @Override - public void run(CompilationUnit compilationUnit) { - compilationUnit.findAll(NameExpr.class).stream() + public void run(Source java) { + java.getModel().findAll(NameExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)")) - .forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr)); - compilationUnit.findAll(FieldAccessExpr.class).stream() + java.getModel().findAll(FieldAccessExpr.class).stream() .filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)")) - .forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString())); + .forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr)); } private Map getModel(NameExpr expression) { diff --git a/src/main/java/com/bartek/esa/di/DependencyInjector.java b/src/main/java/com/bartek/esa/di/DependencyInjector.java index da0baaa..c74cec1 100644 --- a/src/main/java/com/bartek/esa/di/DependencyInjector.java +++ b/src/main/java/com/bartek/esa/di/DependencyInjector.java @@ -3,6 +3,7 @@ package com.bartek.esa.di; import com.bartek.esa.EsaMain; import com.bartek.esa.analyser.di.AnalyserModule; import com.bartek.esa.cli.di.CliModule; +import com.bartek.esa.context.di.ContextModule; import com.bartek.esa.core.di.CoreModule; import com.bartek.esa.core.di.PluginModule; import com.bartek.esa.decompiler.di.DecompilerModule; @@ -19,7 +20,8 @@ import dagger.Component; CoreModule.class, PluginModule.class, AnalyserModule.class, - FormatterModule.class + FormatterModule.class, + ContextModule.class }) public interface DependencyInjector { EsaMain esa(); diff --git a/src/main/java/com/bartek/esa/dispatcher/dispatcher/MethodDispatcher.java b/src/main/java/com/bartek/esa/dispatcher/dispatcher/MethodDispatcher.java index 8c294cd..362eae8 100644 --- a/src/main/java/com/bartek/esa/dispatcher/dispatcher/MethodDispatcher.java +++ b/src/main/java/com/bartek/esa/dispatcher/dispatcher/MethodDispatcher.java @@ -1,6 +1,6 @@ package com.bartek.esa.dispatcher.dispatcher; -import com.bartek.esa.cli.model.CliArgsOptions; +import com.bartek.esa.cli.model.object.CliArgsOptions; import com.bartek.esa.core.model.object.Issue; import com.bartek.esa.dispatcher.model.DispatcherActions; diff --git a/src/main/java/com/bartek/esa/file/di/FileModule.java b/src/main/java/com/bartek/esa/file/di/FileModule.java index 8d927cc..2c81e3b 100644 --- a/src/main/java/com/bartek/esa/file/di/FileModule.java +++ b/src/main/java/com/bartek/esa/file/di/FileModule.java @@ -2,6 +2,7 @@ package com.bartek.esa.file.di; import com.bartek.esa.file.cleaner.FileCleaner; import com.bartek.esa.file.matcher.GlobMatcher; +import com.bartek.esa.file.matcher.PackageNameMatcher; import com.bartek.esa.file.provider.FileContentProvider; import com.bartek.esa.file.provider.FileProvider; import com.bartek.esa.file.zip.ZipTool; @@ -35,4 +36,9 @@ public class FileModule { public FileCleaner fileCleaner() { return new FileCleaner(); } + + @Provides + public PackageNameMatcher packageNameMatcher() { + return new PackageNameMatcher(); + } } diff --git a/src/main/java/com/bartek/esa/file/matcher/PackageNameMatcher.java b/src/main/java/com/bartek/esa/file/matcher/PackageNameMatcher.java new file mode 100644 index 0000000..e5b712b --- /dev/null +++ b/src/main/java/com/bartek/esa/file/matcher/PackageNameMatcher.java @@ -0,0 +1,16 @@ +package com.bartek.esa.file.matcher; + +import javax.inject.Inject; +import java.io.File; + +public class PackageNameMatcher { + + @Inject + public PackageNameMatcher() { + + } + + public boolean doesFileMatchPackageName(File file, String packageName) { + return file.getAbsolutePath().replaceAll(File.separator, ".").contains(packageName); + } +} diff --git a/src/main/java/com/bartek/esa/formatter/archetype/Formatter.java b/src/main/java/com/bartek/esa/formatter/archetype/Formatter.java index a84e644..2a2ee18 100644 --- a/src/main/java/com/bartek/esa/formatter/archetype/Formatter.java +++ b/src/main/java/com/bartek/esa/formatter/archetype/Formatter.java @@ -5,5 +5,7 @@ import com.bartek.esa.core.model.object.Issue; import java.util.Set; public interface Formatter { - void format(Set issues); + void beforeFormat(); + String format(Set issues); + void afterFormat(); } diff --git a/src/main/java/com/bartek/esa/formatter/di/FormatterModule.java b/src/main/java/com/bartek/esa/formatter/di/FormatterModule.java index b7741aa..e51a869 100644 --- a/src/main/java/com/bartek/esa/formatter/di/FormatterModule.java +++ b/src/main/java/com/bartek/esa/formatter/di/FormatterModule.java @@ -2,6 +2,7 @@ package com.bartek.esa.formatter.di; import com.bartek.esa.core.desc.provider.DescriptionProvider; import com.bartek.esa.formatter.formatter.ColorFormatter; +import com.bartek.esa.formatter.formatter.JsonFormatter; import com.bartek.esa.formatter.formatter.SimpleFormatter; import com.bartek.esa.formatter.provider.FormatterProvider; import dagger.Module; @@ -21,7 +22,12 @@ public class FormatterModule { } @Provides - public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) { - return new FormatterProvider(simpleFormatter, colorFormatter); + public JsonFormatter jsonFormatter() { + return new JsonFormatter(); + } + + @Provides + public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) { + return new FormatterProvider(simpleFormatter, colorFormatter, jsonFormatter); } } diff --git a/src/main/java/com/bartek/esa/formatter/formatter/ColorFormatter.java b/src/main/java/com/bartek/esa/formatter/formatter/ColorFormatter.java index 8b56f46..33091aa 100644 --- a/src/main/java/com/bartek/esa/formatter/formatter/ColorFormatter.java +++ b/src/main/java/com/bartek/esa/formatter/formatter/ColorFormatter.java @@ -30,12 +30,15 @@ public class ColorFormatter implements Formatter { } @Override - public void format(Set issues) { + public void beforeFormat() { AnsiConsole.systemInstall(); + } + + @Override + public String format(Set issues) { if (issues.isEmpty()) { Ansi noIssuesFound = ansi().fg(GREEN).a("No issues found.").reset(); - System.out.println(noIssuesFound); - return; + return noIssuesFound.toString(); } String format = issues.stream() @@ -43,8 +46,11 @@ public class ColorFormatter implements Formatter { .map(this::format) .collect(Collectors.joining()); - System.out.println(format.substring(0, format.length() - 2)); - System.out.println(printSummary(issues)); + return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues)); + } + + @Override + public void afterFormat() { AnsiConsole.systemUninstall(); } diff --git a/src/main/java/com/bartek/esa/formatter/formatter/JsonFormatter.java b/src/main/java/com/bartek/esa/formatter/formatter/JsonFormatter.java new file mode 100644 index 0000000..d94f88f --- /dev/null +++ b/src/main/java/com/bartek/esa/formatter/formatter/JsonFormatter.java @@ -0,0 +1,29 @@ +package com.bartek.esa.formatter.formatter; + +import com.bartek.esa.core.model.object.Issue; +import com.bartek.esa.error.EsaException; +import com.bartek.esa.formatter.archetype.Formatter; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.vavr.control.Try; + +import java.util.Set; + +public class JsonFormatter implements Formatter { + + @Override + public void beforeFormat() { + // nothing to do + } + + @Override + public String format(Set issues) { + ObjectMapper objectMapper = new ObjectMapper(); + return Try.of(() -> objectMapper.writeValueAsString(issues)) + .getOrElseThrow(EsaException::new); + } + + @Override + public void afterFormat() { + // nothing to do + } +} diff --git a/src/main/java/com/bartek/esa/formatter/formatter/SimpleFormatter.java b/src/main/java/com/bartek/esa/formatter/formatter/SimpleFormatter.java index 7644ce0..448284b 100644 --- a/src/main/java/com/bartek/esa/formatter/formatter/SimpleFormatter.java +++ b/src/main/java/com/bartek/esa/formatter/formatter/SimpleFormatter.java @@ -20,10 +20,14 @@ public class SimpleFormatter implements Formatter { } @Override - public void format(Set issues) { + public void beforeFormat() { + // nothing to do + } + + @Override + public String format(Set issues) { if (issues.isEmpty()) { - System.out.println("No issues found."); - return; + return "No issues found."; } String format = issues.stream() @@ -31,8 +35,12 @@ public class SimpleFormatter implements Formatter { .map(this::format) .collect(Collectors.joining()); - System.out.println(format.substring(0, format.length() - 2)); - System.out.println(printSummary(issues)); + return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues)); + } + + @Override + public void afterFormat() { + // nothing to do } private String format(Issue issue) { diff --git a/src/main/java/com/bartek/esa/formatter/provider/FormatterProvider.java b/src/main/java/com/bartek/esa/formatter/provider/FormatterProvider.java index 16a31ce..0b1ad1c 100644 --- a/src/main/java/com/bartek/esa/formatter/provider/FormatterProvider.java +++ b/src/main/java/com/bartek/esa/formatter/provider/FormatterProvider.java @@ -1,8 +1,9 @@ package com.bartek.esa.formatter.provider; -import com.bartek.esa.cli.model.CliArgsOptions; +import com.bartek.esa.cli.model.object.CliArgsOptions; import com.bartek.esa.formatter.archetype.Formatter; import com.bartek.esa.formatter.formatter.ColorFormatter; +import com.bartek.esa.formatter.formatter.JsonFormatter; import com.bartek.esa.formatter.formatter.SimpleFormatter; import javax.inject.Inject; @@ -10,14 +11,24 @@ import javax.inject.Inject; public class FormatterProvider { private final SimpleFormatter simpleFormatter; private final ColorFormatter colorFormatter; + private final JsonFormatter jsonFormatter; @Inject - public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) { + public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) { this.simpleFormatter = simpleFormatter; this.colorFormatter = colorFormatter; + this.jsonFormatter = jsonFormatter; } public Formatter provide(CliArgsOptions options) { - return options.isColor() ? colorFormatter : simpleFormatter; + switch (options.getOutputType()) { + case COLOR: + return colorFormatter; + case JSON: + return jsonFormatter; + case DEFAULT: + default: + return simpleFormatter; + } } } diff --git a/src/main/resources/description.properties b/src/main/resources/description.properties index 04612e4..d916ca2 100644 --- a/src/main/resources/description.properties +++ b/src/main/resources/description.properties @@ -93,6 +93,13 @@ com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION=Exported ${com It means any malicious application could make use of the component. \n\ Consider using 'android:permission' tag and adding custom permission to protect it. +com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION.DATA_USAGE=Exported ${componentType} makes use of data of incoming intents.\n\ + The ${componentType} with name '${componentName}' is exported but not protected by any permission. \n\ + It probably does also process a data from incoming intent. \n\ + It means any malicious application could make use of the component or inject a fake (potentially malicious) data to it. \n\ + Consider using 'android:permission' tag and adding custom permission to protect it. \n\ + Also make sure, that intent is correctly validated before processing it. + com.bartek.esa.core.plugin.DangerousPermissionPlugin=Custom permission without description.\n\ Custom permission with 'dangerous' protection level was found and it doesn't have any description.\n\ As long as the permission requires user attention, he should have provided a meaningful description about\n\ @@ -103,12 +110,22 @@ com.bartek.esa.core.plugin.TextInputValidationPlugin=Input type is no selected.\ Consider associating a input type with this view.\n\ For example: