From e3358d0c6eb6cd9f091fd0798714765eef90e7c3 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Thu, 20 Jul 2023 22:44:19 +0300 Subject: [PATCH 1/7] Simple straitforward yaml serialization with minimal needed functionality --- .../main/java/brut/androlib/ApkDecoder.java | 10 +- .../main/java/brut/androlib/apk/ApkInfo.java | 126 +++++++++-- .../java/brut/androlib/apk/PackageInfo.java | 26 ++- .../java/brut/androlib/apk/UsesFramework.java | 25 ++- .../java/brut/androlib/apk/VersionInfo.java | 25 ++- .../main/java/brut/androlib/apk/YamlLine.java | 87 ++++++++ .../java/brut/androlib/apk/YamlReader.java | 202 ++++++++++++++++++ .../brut/androlib/apk/YamlSerializable.java | 8 + .../java/brut/androlib/apk/YamlWriter.java | 124 +++++++++++ .../brut/androlib/apk/ApkInfoReaderTest.java | 132 ++++++++++++ .../java/brut/androlib/apk/YamlLineTest.java | 104 +++++++++ .../brut/androlib/yaml/MaliciousYamlTest.java | 3 +- .../resources/apk/first_incorrect_indent.yml | 22 ++ .../test/resources/apk/list_with_indent.yml | 26 +++ .../resources/apk/skip_incorrect_indent.yml | 22 ++ .../src/test/resources/apk/standard.yml | 22 ++ .../src/test/resources/apk/unknown_fields.yml | 25 +++ .../src/test/resources/apk/unknown_files.yml | 25 +++ 18 files changed, 990 insertions(+), 24 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlSerializable.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/list_with_indent.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/standard.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/unknown_fields.yml create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/unknown_files.yml diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index ba1379c6fc..1d9417de68 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -165,7 +165,7 @@ public ApkInfo decode(File outDir) throws AndrolibException, IOException, Direct copyRawFiles(outDir); copyUnknownFiles(apkInfo, outDir); - Collection mUncompressedFiles = new ArrayList<>(); + List mUncompressedFiles = new ArrayList<>(); recordUncompressedFiles(apkInfo, resourcesDecoder.getResFileMapping(), mUncompressedFiles); copyOriginalFiles(outDir); writeApkInfo(apkInfo, outDir); @@ -220,11 +220,7 @@ private boolean hasMultipleSources() throws AndrolibException { } private void writeApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException { - try { - apkInfo.save(new File(outDir, "apktool.yml")); - } catch (IOException ex) { - throw new AndrolibException(ex); - } + apkInfo.save(new File(outDir, "apktool.yml")); } private void copyManifestRaw(File outDir) @@ -376,7 +372,7 @@ private void copyOriginalFiles(File outDir) private void recordUncompressedFiles(ApkInfo apkInfo, Map resFileMapping, - Collection uncompressedFilesOrExts) + List uncompressedFilesOrExts) throws AndrolibException { try { Directory unk = mApkFile.getDirectory(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index 4ec70c23eb..c705cad08c 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -27,12 +27,11 @@ import org.yaml.snakeyaml.introspector.PropertyUtils; import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -public class ApkInfo { +public class ApkInfo implements YamlSerializable { public String version; private String apkFileName; @@ -45,7 +44,7 @@ public class ApkInfo { public boolean sharedLibrary; public boolean sparseResources; public Map unknownFiles; - public Collection doNotCompress; + public List doNotCompress; /** @deprecated use {@link #resourcesAreCompressed} */ public boolean compressionType; @@ -157,28 +156,129 @@ private int mapSdkShorthandToVersion(String sdkVersion) { } } - public void save(File file) throws IOException { - try( - FileOutputStream fos = new FileOutputStream(file); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8); - Writer writer = new BufferedWriter(outputStreamWriter) +// public void save(File file) throws IOException { +// try( +// FileOutputStream fos = new FileOutputStream(file); +// OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8); +// Writer writer = new BufferedWriter(outputStreamWriter) +// ) { +// save(writer); +// } +// } + + public void save(File file) throws AndrolibException { + try ( + YamlWriter writer = new YamlWriter(new FileOutputStream(file)); ) { - save(writer); + write(writer); + } catch (FileNotFoundException e) { + throw new AndrolibException("File not found"); + } catch (Exception e) { + throw new AndrolibException(e); } } - public static ApkInfo load(InputStream is) { - return getYaml().loadAs(is, ApkInfo.class); + public static ApkInfo load(InputStream is) throws AndrolibException { + // return getYaml().loadAs(is, ApkInfo.class); + YamlReader reader = new YamlReader(is); + ApkInfo apkInfo = new ApkInfo(); + return reader.readRoot(apkInfo); } +// public static ApkInfo load(File appDir) +// throws AndrolibException { +// try( +// InputStream in = new FileDirectory(appDir).getFileInput("apktool.yml") +// ) { +// return ApkInfo.load(in); +// } catch (DirectoryException | IOException ex) { +// throw new AndrolibException(ex); +// } +// } + public static ApkInfo load(File appDir) throws AndrolibException { try( - InputStream in = new FileDirectory(appDir).getFileInput("apktool.yml") + InputStream in = new FileDirectory(appDir).getFileInput("apktool.yml"); ) { return ApkInfo.load(in); } catch (DirectoryException | IOException ex) { throw new AndrolibException(ex); } } + + @Override + public void readItem(YamlReader reader) throws AndrolibException { + YamlLine line = reader.getLine(); + switch (line.key) { + case "version": { + this.version = line.getValueString(); + break; + } + case "apkFileName": { + this.apkFileName = line.getValueString(); + break; + } + case "isFrameworkApk": { + this.isFrameworkApk = line.getValueBool(); + break; + } + case "usesFramework": { + this.usesFramework = new UsesFramework(); + reader.readObject(usesFramework); + break; + } + case "sdkInfo": { + this.sdkInfo = reader.readMap(); + break; + } + case "packageInfo": { + this.packageInfo = new PackageInfo(); + reader.readObject(packageInfo); + break; + } + case "versionInfo": { + this.versionInfo = new VersionInfo(); + reader.readObject(versionInfo); + break; + } + case "compressionType": + case "resourcesAreCompressed": { + this.resourcesAreCompressed = line.getValueBool(); + break; + } + case "sharedLibrary": { + this.sharedLibrary = line.getValueBool(); + break; + } + case "sparseResources": { + this.sparseResources = line.getValueBool(); + break; + } + case "unknownFiles": { + this.unknownFiles = reader.readMap(); + break; + } + case "doNotCompress": { + this.doNotCompress = reader.readStringList(); + break; + } + } + } + + @Override + public void write(YamlWriter writer) { + writer.writeString("version", version); + writer.writeString("apkFileName", apkFileName); + writer.writeBool("isFrameworkApk", isFrameworkApk); + writer.writeObject("usesFramework", usesFramework); + writer.writeStringMap("sdkInfo", sdkInfo); + writer.writeObject("packageInfo", packageInfo); + writer.writeObject("versionInfo", versionInfo); + writer.writeBool("resourcesAreCompressed", resourcesAreCompressed); + writer.writeBool("sharedLibrary", sharedLibrary); + writer.writeBool("sparseResources", sparseResources); + writer.writeStringMap("unknownFiles", unknownFiles); + writer.writeList("doNotCompress", doNotCompress); + } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java index 364a9afc30..485b8351fe 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java @@ -16,7 +16,31 @@ */ package brut.androlib.apk; -public class PackageInfo { +import brut.androlib.exceptions.AndrolibException; + +public class PackageInfo implements YamlSerializable { public String forcedPackageId; public String renameManifestPackage; + + @Override + public void readItem(YamlReader reader) throws AndrolibException { + YamlLine line = reader.getLine(); + switch (line.key) { + case "forcedPackageId": { + forcedPackageId = line.getValueString(); + break; + } + case "renameManifestPackage": { + renameManifestPackage = line.getValueString(); + break; + } + } + } + + @Override + public void write(YamlWriter writer) { + writer.writeString("forcedPackageId", forcedPackageId); + writer.writeString("renameManifestPackage", renameManifestPackage); + } + } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java index c3bfb2da17..f6f56b6b37 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java @@ -16,9 +16,32 @@ */ package brut.androlib.apk; +import brut.androlib.exceptions.AndrolibException; + import java.util.List; -public class UsesFramework { +public class UsesFramework implements YamlSerializable { public List ids; public String tag; + + @Override + public void readItem(YamlReader reader) throws AndrolibException { + YamlLine line = reader.getLine(); + switch (line.key) { + case "ids": { + ids = reader.readIntList(); + break; + } + case "tag": { + tag = line.getValueString(); + break; + } + } + } + + @Override + public void write(YamlWriter writer) { + writer.writeList("ids", ids); + writer.writeString("tag", tag); + } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java index 3fd488bf52..ac83601800 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java @@ -16,7 +16,30 @@ */ package brut.androlib.apk; -public class VersionInfo { +import brut.androlib.exceptions.AndrolibException; + +public class VersionInfo implements YamlSerializable { public String versionCode; public String versionName; + + @Override + public void readItem(YamlReader reader) throws AndrolibException { + YamlLine line = reader.getLine(); + switch (line.key) { + case "versionCode": { + versionCode = line.getValueString(); + break; + } + case "versionName": { + versionName = line.getValueString(); + break; + } + } + } + + @Override + public void write(YamlWriter writer) { + writer.writeString("versionCode", versionCode); + writer.writeString("versionName", versionName); + } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java new file mode 100644 index 0000000000..d8d7536f95 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java @@ -0,0 +1,87 @@ +package brut.androlib.apk; + +import java.util.Objects; + +public class YamlLine { + + public int indent = 0; + public String key = ""; + public String value = ""; + public boolean isComment; + public boolean isEmpty; + public boolean hasColon; + public boolean isNull; + public boolean isItem; + + public YamlLine(String line) { + // special end line marker + isNull = Objects.isNull(line); + if (isNull) { + return; + } + isEmpty = line.trim().isEmpty(); + if (isEmpty) { + return; + } + // count indent - space only + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) == ' ') { + indent++; + } else { + break; + } + } + // remove whitespace + line = line.trim(); + char first = line.charAt(0); + + isComment = first == '#' || first == '!'; + isItem = first == '-'; + if (isComment) { + // for comment fill value + value = line.substring(1).trim(); + } else { + // value line + hasColon = line.contains(":"); + if (isItem) { + // array item line has only the value + value = line.substring(1).trim(); + } else { + // split line to key - value + String[] parts = line.split(":"); + if (parts.length > 0) { + key = parts[0].trim(); + if (parts.length > 1) { + value = parts[1].trim(); + } + } + } + } + } + + public String getValueString() { + if (value.equals("null")) + return null; + String res = YamlStringEscapeUtils.unescapeString(value); + // remove quotation marks + res = res.replaceAll("^\"|\"$", ""); + res = res.replaceAll("^'|'$", ""); + return res; + } + + public String getKeyString() { + String res = YamlStringEscapeUtils.unescapeString(key); + // remove quotation marks + res = res.replaceAll("^\"|\"$", ""); + res = res.replaceAll("^'|'$", ""); + return res; + } + + public boolean getValueBool() { + return Objects.equals(value, "true"); + } + + public int getValueInt() { + return Integer.parseInt(value); + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java new file mode 100644 index 0000000000..f4ef505b9e --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java @@ -0,0 +1,202 @@ +package brut.androlib.apk; + +import brut.androlib.exceptions.AndrolibException; + +import java.io.InputStream; +import java.util.*; + +public class YamlReader { + + private ArrayList mLines; + private int mCurrent = 0; + + public YamlReader(InputStream in) { + mLines = new ArrayList<>(); + mLines.add(new YamlLine(null)); + read(in); + } + + public int getLineNo() { + return mCurrent + 1; + } + + public void pushLine() { + if (mCurrent > 0) + mCurrent--; + } + + public void read(InputStream in) { + Scanner scanner = new Scanner(in); + mLines = new ArrayList<>(); + while (scanner.hasNextLine()) { + mLines.add(new YamlLine(scanner.nextLine())); + } + mLines.add(new YamlLine(null)); + } + + public YamlLine getLine() { + return mLines.get(mCurrent); + } + + public int getIndent() { + return getLine().indent; + } + + public boolean isEnd() { + return getLine().isNull; + } + + public boolean isCommentOrEmpty() { + YamlLine line = getLine(); + return line.isEmpty || line.isComment; + } + + public void skipInsignificant() { + if (isEnd()) + return; + while (isCommentOrEmpty()) { + mCurrent++; + if (isEnd()) + break; + } + } + + public boolean nextLine() { + if (isEnd()) + return false; + while (true) { + mCurrent++; + // skip comments + if (isCommentOrEmpty()) + continue; + return !isEnd(); + } + } + + interface Checker { + boolean check(YamlLine line); + } + + interface Updater { + void update(T items, YamlReader reader) throws AndrolibException; + } + + /** + * Read root object from start to end + */ + public T readRoot(T obj) throws AndrolibException { + if (isEnd()) + return obj; + int objIndent = 0; + skipInsignificant(); + while (true) { + if (isEnd()) + return obj; + YamlLine line = getLine(); + // skip don't checked line or lines with other indent + if (objIndent != line.indent || !line.hasColon) { + nextLine(); + continue; + } + obj.readItem(this); + nextLine(); + } + } + + /** + * Read object. Reader stand on the object name. + * The object data should be placed on the next line + * and have indent. + */ + public T readObject(T obj, + Checker check, + Updater updater) throws AndrolibException { + if (isEnd()) + return obj; + int prevIndent = getIndent(); + // detect indent for the object data + nextLine(); + YamlLine line = getLine(); + int objIndent = line.indent; + // object data must have indent + // otherwise stop reading + if (objIndent <= prevIndent || !check.check(line)) { + pushLine(); + return obj; + } + updater.update(obj, this); + while (nextLine()) { + if (isEnd()) + return obj; + line = getLine(); + if (objIndent != line.indent || !check.check(line)) { + pushLine(); + return obj; + } + updater.update(obj, this); + } + return obj; + } + + T readObject(T obj) throws AndrolibException { + return readObject(obj, + line -> line.hasColon, + YamlSerializable::readItem); + } + + /** + * Read list. Reader stand on the object name. + * The list data should be placed on the next line. + * Data should have same indent. May by same with name. + */ + public List readList(List list, + Updater> updater) throws AndrolibException { + if (isEnd()) + return list; + int listIndent = getIndent(); + nextLine(); + int dataIndent = getIndent(); + while (true) { + if (isEnd()) + return list; + // check incorrect data indent + if (dataIndent < listIndent) { + pushLine(); + return list; + } + YamlLine line = getLine(); + if (dataIndent != line.indent || !line.isItem) { + pushLine(); + return list; + } + updater.update(list, this); + nextLine(); + } + } + + public List readStringList() throws AndrolibException { + List list = new ArrayList<>(); + return readList(list, + (items, reader) -> { + items.add(reader.getLine().getValueString()); + }); + }; + + public List readIntList() throws AndrolibException { + List list = new ArrayList<>(); + return readList(list, + (items, reader) -> { + items.add(reader.getLine().getValueInt()); + }); + }; + + public Map readMap() throws AndrolibException { + Map map = new LinkedHashMap<>(); + return readObject(map, + line -> line.hasColon, + (items, reader) -> { + YamlLine line = reader.getLine(); + items.put(line.getKeyString(), line.getValueString()); + }); + }; +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlSerializable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlSerializable.java new file mode 100644 index 0000000000..2ff10760db --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlSerializable.java @@ -0,0 +1,8 @@ +package brut.androlib.apk; + +import brut.androlib.exceptions.AndrolibException; + +public interface YamlSerializable { + void readItem(YamlReader reader) throws AndrolibException; + void write(YamlWriter writer); +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java new file mode 100644 index 0000000000..25fd713149 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java @@ -0,0 +1,124 @@ +package brut.androlib.apk; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class YamlWriter implements AutoCloseable { + + private int mIndent = 0; + private final PrintWriter mWriter; + private final String QUOTE = "'"; + + public YamlWriter(OutputStream out) { + mWriter = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(out, StandardCharsets.UTF_8))); + } + + @Override + public void close() throws Exception { + mWriter.close(); + } + + public int getIndent() { + return mIndent; + } + + public String getIndentString() { + // for java 11 + // return " ".repeat(mIndent); + // for java 8 + return String.join("", Collections.nCopies(mIndent, " ")); + } + + public void nextIndent() { + mIndent += 2; + } + + public void prevIndent() { + if (mIndent != 0) + mIndent -= 2; + } + + public void writeIndent() { + mWriter.print(getIndentString()); + } + + public void writeInt(String key, int value) { + writeIndent(); + mWriter.println(key + ": " + value); + } + + public void writeBool(String key, boolean value) { + writeIndent(); + String val = value ? "true": "false"; + mWriter.println(key + ": " + val); + } + + public void writeString(String key, String value, boolean quoted) { + writeIndent(); + if (Objects.isNull(value)) { + mWriter.println(key + ": null"); + } else { + value = YamlStringEscapeUtils.escapeString(value); + if (quoted) + value = QUOTE + value + QUOTE; + mWriter.println(YamlStringEscapeUtils.escapeString(key) + ": " + value); + } + } + + public void writeString(String key, String value) { + writeString(key, value, false); + } + + public void writeList(String key, List list) { + if (Objects.isNull(list)) + return; + writeIndent(); + mWriter.println(key + ":"); + for (T item: list) { + writeIndent(); + mWriter.println("- " + item); + } + } + + public void writeCommonMap(String key, Map map) { + if (Objects.isNull(map)) + return; + writeIndent(); + mWriter.println(key + ":"); + nextIndent(); + for (K mapKey: map.keySet()) { + writeIndent(); + mWriter.println(mapKey + ": " + map.get(mapKey)); + } + prevIndent(); + } + + public void writeStringMap(String key, Map map) { + if (Objects.isNull(map)) + return; + writeIndent(); + mWriter.println(key + ":"); + nextIndent(); + for (String mapKey: map.keySet()) { + writeString(mapKey, map.get(mapKey)); +// writeIndent(); +// String val = map.get(mapKey); +// mapKey = YamlStringEscapeUtils.escapeString(mapKey); +// val = YamlStringEscapeUtils.escapeString(val); +// mWriter.println(mapKey + ": " + val); + } + prevIndent(); + } + + public void writeObject(String key, T obj) { + if (Objects.isNull(obj)) + return; + writeIndent(); + mWriter.println(key + ":"); + nextIndent(); + obj.write(this); + prevIndent(); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java new file mode 100644 index 0000000000..9896ed69b4 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java @@ -0,0 +1,132 @@ +package brut.androlib.apk; + +import brut.androlib.exceptions.AndrolibException; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ApkInfoReaderTest { + + private void checkStandard(ApkInfo apkInfo) { + assertEquals("standard.apk", apkInfo.getApkFileName()); + assertFalse(apkInfo.resourcesAreCompressed); + assertEquals(1, apkInfo.doNotCompress.size()); + assertEquals("resources.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")); + assertFalse(apkInfo.sharedLibrary); + assertFalse(apkInfo.sparseResources); + assertNotNull(apkInfo.usesFramework); + assertNotNull(apkInfo.usesFramework.ids); + assertEquals(1, apkInfo.usesFramework.ids.size()); + assertEquals(1, (long)apkInfo.usesFramework.ids.get(0)); + assertNull(apkInfo.usesFramework.tag); + assertNotNull(apkInfo.versionInfo); + assertNull(apkInfo.versionInfo.versionCode); + assertNull(apkInfo.versionInfo.versionName); + } + + @Test + public void testStandard() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/standard.yml")); + checkStandard(apkInfo); + assertEquals("2.8.1", apkInfo.version); + } + + @Test + public void testUnknownFields() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/unknown_fields.yml")); + checkStandard(apkInfo); + assertEquals("2.8.1", apkInfo.version); + } + + @Test + public void testSkipIncorrectIndent() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml")); + checkStandard(apkInfo); + assertNotEquals("2.8.1", apkInfo.version); + } + + @Test + public void testFirstIncorrectIndent() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/first_incorrect_indent.yml")); + checkStandard(apkInfo); + assertNotEquals("2.8.1", apkInfo.version); + } + + @Test + public void testUnknownFiles() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/unknown_files.yml")); + assertEquals("2.0.0", apkInfo.version); + assertEquals("testapp.apk", apkInfo.getApkFileName()); + assertFalse(apkInfo.isFrameworkApk); + assertNotNull(apkInfo.usesFramework); + assertEquals(1, apkInfo.usesFramework.ids.size()); + assertEquals(1, (long)apkInfo.usesFramework.ids.get(0)); + assertNotNull(apkInfo.packageInfo); + assertEquals("127", apkInfo.packageInfo.forcedPackageId); + 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("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")); + } + + @Test + public void testUlist_with_indent() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/list_with_indent.yml")); + assertEquals("2.8.0", apkInfo.version); + assertEquals("basic.apk", apkInfo.getApkFileName()); + assertFalse(apkInfo.isFrameworkApk); + assertNotNull(apkInfo.usesFramework); + assertEquals(1, apkInfo.usesFramework.ids.size()); + assertEquals(1, (long)apkInfo.usesFramework.ids.get(0)); + assertEquals("tag", apkInfo.usesFramework.tag); + 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")); + 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("png", apkInfo.doNotCompress.get(1)); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java new file mode 100644 index 0000000000..659bc1a588 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java @@ -0,0 +1,104 @@ +package brut.androlib.apk; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class YamlLineTest { + + @Test + public void testEmptyLine() { + YamlLine line = new YamlLine(""); + assertEquals(0, line.indent); + assertTrue(line.isEmpty); + + line = new YamlLine(" "); + assertEquals(0, line.indent); + assertTrue(line.isEmpty); + } + + @Test + public void testComment() { + YamlLine line = new YamlLine("!ApkInfo.class"); + assertTrue(line.isComment); + + line = new YamlLine("# This is comment"); + assertEquals(0, line.indent); + assertTrue(line.isComment); + assertEquals("", line.key); + assertEquals("This is comment", line.value); + + line = new YamlLine(" # This is comment"); + assertEquals(2, line.indent); + assertTrue(line.isComment); + assertEquals("", line.key); + assertEquals("This is comment", line.value); + } + + @Test + public void testKeyLine() { + YamlLine line = new YamlLine("name:"); + assertFalse(line.isComment); + assertEquals(0, line.indent); + assertEquals("name", line.key); + assertEquals("", line.value); + + line = new YamlLine(" name:"); + assertFalse(line.isComment); + assertEquals(2, line.indent); + assertEquals("name", line.key); + assertEquals("", line.value); + + line = new YamlLine(":value"); + assertFalse(line.isComment); + assertEquals(0, line.indent); + assertEquals("", line.key); + assertEquals("value", line.value); + + line = new YamlLine(" : value "); + assertFalse(line.isComment); + assertEquals(2, line.indent); + assertEquals("", line.key); + assertEquals("value", line.value); + + line = new YamlLine("name : value "); + assertFalse(line.isComment); + assertEquals(0, line.indent); + assertEquals("name", line.key); + assertEquals("value", line.value); + + line = new YamlLine(" name : value "); + assertFalse(line.isComment); + assertEquals(2, line.indent); + assertEquals("name", line.key); + assertEquals("value", line.value); + + line = new YamlLine(" name : value ::"); + assertFalse(line.isComment); + assertEquals(2, line.indent); + assertEquals("name", line.key); + assertEquals("value", line.value); + + // split this gives parts.length = 0!! + line = new YamlLine(":::"); + assertFalse(line.isComment); + assertEquals(0, line.indent); + assertEquals("", line.key); + assertEquals("", line.value); + } + + @Test + public void testItemLine() { + YamlLine line = new YamlLine("- val1"); + assertTrue(line.isItem); + assertEquals(0, line.indent); + assertEquals("", line.key); + assertEquals("val1", line.value); + + line = new YamlLine(" - val1: ff"); + assertTrue(line.isItem); + assertEquals(2, line.indent); + assertEquals("", line.key); + assertEquals("val1: ff", line.value); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java index 314e8d1c00..56e1cfd965 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java @@ -41,7 +41,8 @@ public static void beforeClass() throws Exception { TestUtils.copyResourceDir(MaliciousYamlTest.class, "yaml/cve20220476/", sTestNewDir); } - @Test(expected = ConstructorException.class) + //@Test(expected = ConstructorException.class) + @Test public void testMaliciousYamlNotLoaded() throws BrutException { Config config = Config.getDefaultConfig(); File testApk = new File(sTmpDir, "cve20220476.apk"); diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml b/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml new file mode 100644 index 0000000000..373b5fdfa2 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml @@ -0,0 +1,22 @@ +!!brut.androlib.meta.MetaInfo + version: 2.8.1 +apkFileName: standard.apk +compressionType: false +doNotCompress: +- resources.arsc +isFrameworkApk: false +packageInfo: + forcedPackageId: '127' + renameManifestPackage: null +sdkInfo: + minSdkVersion: '25' + targetSdkVersion: '30' +sharedLibrary: false +sparseResources: false +usesFramework: + ids: + - 1 + tag: null +versionInfo: + versionCode: null + versionName: null diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/list_with_indent.yml b/brut.apktool/apktool-lib/src/test/resources/apk/list_with_indent.yml new file mode 100644 index 0000000000..b51612c6bd --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/list_with_indent.yml @@ -0,0 +1,26 @@ +!!brut.androlib.meta.MetaInfo +apkFileName: basic.apk +compressionType: false +doNotCompress: + - resources.arsc + - png +isFrameworkApk: false +packageInfo: + forcedPackageId: '127' + renameManifestPackage: 'com.test.basic' +sdkInfo: + minSdkVersion: '4' + maxSdkVersion: '30' + targetSdkVersion: '22' +sharedLibrary: false +sparseResources: true +unknownFiles: + hidden.file: 1 +usesFramework: + ids: + - 1 + tag: 'tag' +version: 2.8.0 +versionInfo: + versionCode: '71' + versionName: 1.0.70 diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml b/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml new file mode 100644 index 0000000000..3ff6a7be8b --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml @@ -0,0 +1,22 @@ +!!brut.androlib.meta.MetaInfo +apkFileName: standard.apk + version: 2.8.1 +compressionType: false +doNotCompress: +- resources.arsc +isFrameworkApk: false +packageInfo: + forcedPackageId: '127' + renameManifestPackage: null +sdkInfo: + minSdkVersion: '25' + targetSdkVersion: '30' +sharedLibrary: false +sparseResources: false +usesFramework: + ids: + - 1 + tag: null +versionInfo: + versionCode: null + versionName: null diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/standard.yml b/brut.apktool/apktool-lib/src/test/resources/apk/standard.yml new file mode 100644 index 0000000000..6aa78ecb55 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/standard.yml @@ -0,0 +1,22 @@ +!!brut.androlib.meta.MetaInfo +apkFileName: standard.apk +compressionType: false +doNotCompress: +- resources.arsc +isFrameworkApk: false +packageInfo: + forcedPackageId: '127' + renameManifestPackage: null +sdkInfo: + minSdkVersion: '25' + targetSdkVersion: '30' +sharedLibrary: false +sparseResources: false +usesFramework: + ids: + - 1 + tag: null +version: 2.8.1 +versionInfo: + versionCode: null + versionName: null diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/unknown_fields.yml b/brut.apktool/apktool-lib/src/test/resources/apk/unknown_fields.yml new file mode 100644 index 0000000000..876e9fcaef --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/unknown_fields.yml @@ -0,0 +1,25 @@ +!!brut.androlib.meta.MetaInfo +apkFileName: standard.apk +compressionType: false +test: test +doNotCompress: +- resources.arsc +isFrameworkApk: false +packageInfo: + forcedPackageId: '127' + renameManifestPackage: null + test2: test2 +sdkInfo: + minSdkVersion: '25' + targetSdkVersion: '30' +sharedLibrary: false +sparseResources: false +usesFramework: + ids: + - 1 + tag: null + test3: test3 +version: 2.8.1 +versionInfo: + versionCode: null + versionName: null diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/unknown_files.yml b/brut.apktool/apktool-lib/src/test/resources/apk/unknown_files.yml new file mode 100644 index 0000000000..8362ef054a --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/unknown_files.yml @@ -0,0 +1,25 @@ +version: 2.0.0 +apkFileName: testapp.apk +isFrameworkApk: false +usesFramework: + ids: + - 1 +packageInfo: + forcedPackageId: '127' +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' From 23459d3c5b568027d2adf03d92670e07cdf9ee22 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Sat, 22 Jul 2023 12:03:13 +0300 Subject: [PATCH 2/7] Consolidate ApkInfo tests in the package brut.androlib.apk, unify interface YamlReader and add ApkInfoSerializationTest read -> write -> read test --- .../main/java/brut/androlib/apk/ApkInfo.java | 18 +++-- .../java/brut/androlib/apk/PackageInfo.java | 6 +- .../java/brut/androlib/apk/UsesFramework.java | 8 +- .../java/brut/androlib/apk/VersionInfo.java | 6 +- .../main/java/brut/androlib/apk/YamlLine.java | 12 ++- .../java/brut/androlib/apk/YamlReader.java | 50 ++++++------ .../java/brut/androlib/apk/YamlWriter.java | 30 ++++--- .../apk/ApkInfoSerializationTest.java | 79 +++++++++++++++++++ .../{yaml => apk}/ConsistentPropertyTest.java | 28 ++----- .../InvalidSdkBoundingTest.java | 7 +- .../brut/androlib/apk/MaliciousYamlTest.java | 32 ++++++++ .../java/brut/androlib/apk/YamlLineTest.java | 48 +++++------ .../brut/androlib/yaml/MaliciousYamlTest.java | 51 ------------ .../yaml/basic/apktool.yml => apk/basic.yml} | 0 .../apktool.yml => apk/cve20220476.yml} | 0 .../yaml/cve20220476/AndroidManifest.xml | 4 - 16 files changed, 211 insertions(+), 168 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoSerializationTest.java rename brut.apktool/apktool-lib/src/test/java/brut/androlib/{yaml => apk}/ConsistentPropertyTest.java (70%) rename brut.apktool/apktool-lib/src/test/java/brut/androlib/{androlib => apk}/InvalidSdkBoundingTest.java (95%) create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/MaliciousYamlTest.java delete mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java rename brut.apktool/apktool-lib/src/test/resources/{decode/yaml/basic/apktool.yml => apk/basic.yml} (100%) rename brut.apktool/apktool-lib/src/test/resources/{yaml/cve20220476/apktool.yml => apk/cve20220476.yml} (100%) delete mode 100644 brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/AndroidManifest.xml diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index c705cad08c..0a73b320f7 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -27,6 +27,7 @@ import org.yaml.snakeyaml.introspector.PropertyUtils; import java.io.*; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -182,7 +183,8 @@ public static ApkInfo load(InputStream is) throws AndrolibException { // return getYaml().loadAs(is, ApkInfo.class); YamlReader reader = new YamlReader(is); ApkInfo apkInfo = new ApkInfo(); - return reader.readRoot(apkInfo); + reader.readRoot(apkInfo); + return apkInfo; } // public static ApkInfo load(File appDir) @@ -210,13 +212,13 @@ public static ApkInfo load(File appDir) @Override public void readItem(YamlReader reader) throws AndrolibException { YamlLine line = reader.getLine(); - switch (line.key) { + switch (line.getKey()) { case "version": { - this.version = line.getValueString(); + this.version = line.getValue(); break; } case "apkFileName": { - this.apkFileName = line.getValueString(); + this.apkFileName = line.getValue(); break; } case "isFrameworkApk": { @@ -229,7 +231,7 @@ public void readItem(YamlReader reader) throws AndrolibException { break; } case "sdkInfo": { - this.sdkInfo = reader.readMap(); + reader.readMap(sdkInfo); break; } case "packageInfo": { @@ -256,11 +258,13 @@ public void readItem(YamlReader reader) throws AndrolibException { break; } case "unknownFiles": { - this.unknownFiles = reader.readMap(); + this.unknownFiles = new LinkedHashMap<>(); + reader.readMap(unknownFiles); break; } case "doNotCompress": { - this.doNotCompress = reader.readStringList(); + this.doNotCompress = new ArrayList<>(); + reader.readStringList(doNotCompress); break; } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java index 485b8351fe..3ba489a84c 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java @@ -25,13 +25,13 @@ public class PackageInfo implements YamlSerializable { @Override public void readItem(YamlReader reader) throws AndrolibException { YamlLine line = reader.getLine(); - switch (line.key) { + switch (line.getKey()) { case "forcedPackageId": { - forcedPackageId = line.getValueString(); + forcedPackageId = line.getValue(); break; } case "renameManifestPackage": { - renameManifestPackage = line.getValueString(); + renameManifestPackage = line.getValue(); break; } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java index f6f56b6b37..a8582b0339 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java @@ -18,6 +18,7 @@ import brut.androlib.exceptions.AndrolibException; +import java.util.ArrayList; import java.util.List; public class UsesFramework implements YamlSerializable { @@ -27,13 +28,14 @@ public class UsesFramework implements YamlSerializable { @Override public void readItem(YamlReader reader) throws AndrolibException { YamlLine line = reader.getLine(); - switch (line.key) { + switch (line.getKey()) { case "ids": { - ids = reader.readIntList(); + ids = new ArrayList<>(); + reader.readIntList(ids); break; } case "tag": { - tag = line.getValueString(); + tag = line.getValue(); break; } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java index ac83601800..54b6319e53 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java @@ -25,13 +25,13 @@ public class VersionInfo implements YamlSerializable { @Override public void readItem(YamlReader reader) throws AndrolibException { YamlLine line = reader.getLine(); - switch (line.key) { + switch (line.getKey()) { case "versionCode": { - versionCode = line.getValueString(); + versionCode = line.getValue(); break; } case "versionName": { - versionName = line.getValueString(); + versionName = line.getValue(); break; } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java index d8d7536f95..07bce3ef75 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java @@ -5,8 +5,8 @@ public class YamlLine { public int indent = 0; - public String key = ""; - public String value = ""; + private String key = ""; + private String value = ""; public boolean isComment; public boolean isEmpty; public boolean hasColon; @@ -59,7 +59,11 @@ public YamlLine(String line) { } } - public String getValueString() { + public static String unescape(String value) { + return YamlStringEscapeUtils.unescapeString(value); + } + + public String getValue() { if (value.equals("null")) return null; String res = YamlStringEscapeUtils.unescapeString(value); @@ -69,7 +73,7 @@ public String getValueString() { return res; } - public String getKeyString() { + public String getKey() { String res = YamlStringEscapeUtils.unescapeString(key); // remove quotation marks res = res.replaceAll("^\"|\"$", ""); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java index f4ef505b9e..2222dff412 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java @@ -84,14 +84,14 @@ interface Updater { /** * Read root object from start to end */ - public T readRoot(T obj) throws AndrolibException { + public void readRoot(T obj) throws AndrolibException { if (isEnd()) - return obj; + return; int objIndent = 0; skipInsignificant(); while (true) { if (isEnd()) - return obj; + return; YamlLine line = getLine(); // skip don't checked line or lines with other indent if (objIndent != line.indent || !line.hasColon) { @@ -108,11 +108,11 @@ public T readRoot(T obj) throws AndrolibException { * The object data should be placed on the next line * and have indent. */ - public T readObject(T obj, + public void readObject(T obj, Checker check, Updater updater) throws AndrolibException { if (isEnd()) - return obj; + return; int prevIndent = getIndent(); // detect indent for the object data nextLine(); @@ -122,24 +122,23 @@ public T readObject(T obj, // otherwise stop reading if (objIndent <= prevIndent || !check.check(line)) { pushLine(); - return obj; + return; } updater.update(obj, this); while (nextLine()) { if (isEnd()) - return obj; + return; line = getLine(); if (objIndent != line.indent || !check.check(line)) { pushLine(); - return obj; + return; } updater.update(obj, this); } - return obj; } - T readObject(T obj) throws AndrolibException { - return readObject(obj, + void readObject(T obj) throws AndrolibException { + readObject(obj, line -> line.hasColon, YamlSerializable::readItem); } @@ -149,54 +148,51 @@ T readObject(T obj) throws AndrolibException { * The list data should be placed on the next line. * Data should have same indent. May by same with name. */ - public List readList(List list, + public void readList(List list, Updater> updater) throws AndrolibException { if (isEnd()) - return list; + return; int listIndent = getIndent(); nextLine(); int dataIndent = getIndent(); while (true) { if (isEnd()) - return list; + return; // check incorrect data indent if (dataIndent < listIndent) { pushLine(); - return list; + return; } YamlLine line = getLine(); if (dataIndent != line.indent || !line.isItem) { pushLine(); - return list; + return; } updater.update(list, this); nextLine(); } } - public List readStringList() throws AndrolibException { - List list = new ArrayList<>(); - return readList(list, + public void readStringList(List list) throws AndrolibException { + readList(list, (items, reader) -> { - items.add(reader.getLine().getValueString()); + items.add(reader.getLine().getValue()); }); }; - public List readIntList() throws AndrolibException { - List list = new ArrayList<>(); - return readList(list, + public void readIntList(List list) throws AndrolibException { + readList(list, (items, reader) -> { items.add(reader.getLine().getValueInt()); }); }; - public Map readMap() throws AndrolibException { - Map map = new LinkedHashMap<>(); - return readObject(map, + public void readMap(Map map) throws AndrolibException { + readObject(map, line -> line.hasColon, (items, reader) -> { YamlLine line = reader.getLine(); - items.put(line.getKeyString(), line.getValueString()); + items.put(line.getKey(), line.getValue()); }); }; } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java index 25fd713149..bb6629b28e 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java @@ -4,7 +4,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; -public class YamlWriter implements AutoCloseable { +public class YamlWriter implements Closeable { private int mIndent = 0; private final PrintWriter mWriter; @@ -16,7 +16,7 @@ public YamlWriter(OutputStream out) { } @Override - public void close() throws Exception { + public void close() throws IOException { mWriter.close(); } @@ -31,6 +31,10 @@ public String getIndentString() { return String.join("", Collections.nCopies(mIndent, " ")); } + public static String escape(String value) { + return YamlStringEscapeUtils.escapeString(value); + } + public void nextIndent() { mIndent += 2; } @@ -46,24 +50,23 @@ public void writeIndent() { public void writeInt(String key, int value) { writeIndent(); - mWriter.println(key + ": " + value); + mWriter.println(escape(key) + ": " + value); } public void writeBool(String key, boolean value) { writeIndent(); String val = value ? "true": "false"; - mWriter.println(key + ": " + val); + mWriter.println(escape(key) + ": " + val); } public void writeString(String key, String value, boolean quoted) { writeIndent(); if (Objects.isNull(value)) { - mWriter.println(key + ": null"); + mWriter.println(escape(key) + ": null"); } else { - value = YamlStringEscapeUtils.escapeString(value); if (quoted) value = QUOTE + value + QUOTE; - mWriter.println(YamlStringEscapeUtils.escapeString(key) + ": " + value); + mWriter.println(escape(key) + ": " + escape(value)); } } @@ -75,7 +78,7 @@ public void writeList(String key, List list) { if (Objects.isNull(list)) return; writeIndent(); - mWriter.println(key + ":"); + mWriter.println(escape(key) + ":"); for (T item: list) { writeIndent(); mWriter.println("- " + item); @@ -86,7 +89,7 @@ public void writeCommonMap(String key, Map map) { if (Objects.isNull(map)) return; writeIndent(); - mWriter.println(key + ":"); + mWriter.println(escape(key) + ":"); nextIndent(); for (K mapKey: map.keySet()) { writeIndent(); @@ -99,15 +102,10 @@ public void writeStringMap(String key, Map map) { if (Objects.isNull(map)) return; writeIndent(); - mWriter.println(key + ":"); + mWriter.println(escape(key) + ":"); nextIndent(); for (String mapKey: map.keySet()) { writeString(mapKey, map.get(mapKey)); -// writeIndent(); -// String val = map.get(mapKey); -// mapKey = YamlStringEscapeUtils.escapeString(mapKey); -// val = YamlStringEscapeUtils.escapeString(val); -// mWriter.println(mapKey + ": " + val); } prevIndent(); } @@ -116,7 +114,7 @@ public void writeObject(String key, T obj) { if (Objects.isNull(obj)) return; writeIndent(); - mWriter.println(key + ":"); + mWriter.println(escape(key) + ":"); nextIndent(); obj.write(this); prevIndent(); diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoSerializationTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoSerializationTest.java new file mode 100644 index 0000000000..9c9b9739c2 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoSerializationTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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.apk; + +import brut.androlib.exceptions.AndrolibException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.*; + +import static org.junit.Assert.*; + +public class ApkInfoSerializationTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void checkApkInfoSerialization() throws IOException, AndrolibException { + ApkInfo control = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/unknown_files.yml")); + check(control); + + File savedApkInfo = folder.newFile( "saved.yml" ); + control.save(savedApkInfo); + try ( + FileInputStream fis = new FileInputStream(savedApkInfo); + ) { + ApkInfo saved = ApkInfo.load(fis); + check(saved); + } + } + + private void check(ApkInfo apkInfo) { + assertEquals("2.0.0", apkInfo.version); + assertEquals("testapp.apk", apkInfo.getApkFileName()); + assertFalse(apkInfo.isFrameworkApk); + assertNotNull(apkInfo.usesFramework); + assertEquals(1, apkInfo.usesFramework.ids.size()); + assertEquals(1, (long)apkInfo.usesFramework.ids.get(0)); + assertNotNull(apkInfo.packageInfo); + assertEquals("127", apkInfo.packageInfo.forcedPackageId); + 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("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")); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/ConsistentPropertyTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ConsistentPropertyTest.java similarity index 70% rename from brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/ConsistentPropertyTest.java rename to brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ConsistentPropertyTest.java index 618b5ba75c..cca483bb73 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/ConsistentPropertyTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ConsistentPropertyTest.java @@ -14,35 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.yaml; +package brut.androlib.apk; -import brut.androlib.BaseTest; -import brut.androlib.TestUtils; -import brut.androlib.apk.ApkInfo; -import brut.common.BrutException; -import brut.directory.ExtFile; -import brut.util.OS; -import org.junit.BeforeClass; +import brut.androlib.exceptions.AndrolibException; import org.junit.Test; -import java.io.File; import static org.junit.Assert.*; -public class ConsistentPropertyTest extends BaseTest { - - @BeforeClass - public static void beforeClass() throws Exception { - TestUtils.cleanFrameworkFile(); - - sTmpDir = new ExtFile(OS.createTempDirectory()); - sTestNewDir = new ExtFile(sTmpDir, "yaml"); - LOGGER.info("Unpacking yaml files..."); - TestUtils.copyResourceDir(ConsistentPropertyTest.class, "decode/yaml/", sTestNewDir); - } +public class ConsistentPropertyTest { @Test - public void testAssertingAllKnownApkInfoProperties() throws BrutException { - ApkInfo apkInfo = ApkInfo.load(new File(sTestNewDir, "basic")); + public void testAssertingAllKnownApkInfoProperties() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/basic.yml")); assertEquals("2.8.0", apkInfo.version); assertEquals("basic.apk", apkInfo.getApkFileName()); diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/InvalidSdkBoundingTest.java similarity index 95% rename from brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java rename to brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/InvalidSdkBoundingTest.java index f9130f44bf..d3f4c85024 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/InvalidSdkBoundingTest.java @@ -14,17 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.androlib; +package brut.androlib.apk; -import brut.androlib.BaseTest; -import brut.androlib.apk.ApkInfo; import org.junit.Test; import java.util.LinkedHashMap; import java.util.Map; + import static org.junit.Assert.assertEquals; -public class InvalidSdkBoundingTest extends BaseTest { +public class InvalidSdkBoundingTest { @Test public void checkIfInvalidValuesPass() { diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/MaliciousYamlTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/MaliciousYamlTest.java new file mode 100644 index 0000000000..66a53c6a69 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/MaliciousYamlTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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.apk; + +import brut.androlib.exceptions.AndrolibException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MaliciousYamlTest { + + @Test + public void testMaliciousYaml() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/cve20220476.yml")); + assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java index 659bc1a588..1e261a17bd 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/YamlLineTest.java @@ -25,14 +25,14 @@ public void testComment() { line = new YamlLine("# This is comment"); assertEquals(0, line.indent); assertTrue(line.isComment); - assertEquals("", line.key); - assertEquals("This is comment", line.value); + assertEquals("", line.getKey()); + assertEquals("This is comment", line.getValue()); line = new YamlLine(" # This is comment"); assertEquals(2, line.indent); assertTrue(line.isComment); - assertEquals("", line.key); - assertEquals("This is comment", line.value); + assertEquals("", line.getKey()); + assertEquals("This is comment", line.getValue()); } @Test @@ -40,51 +40,51 @@ public void testKeyLine() { YamlLine line = new YamlLine("name:"); assertFalse(line.isComment); assertEquals(0, line.indent); - assertEquals("name", line.key); - assertEquals("", line.value); + assertEquals("name", line.getKey()); + assertEquals("", line.getValue()); line = new YamlLine(" name:"); assertFalse(line.isComment); assertEquals(2, line.indent); - assertEquals("name", line.key); - assertEquals("", line.value); + assertEquals("name", line.getKey()); + assertEquals("", line.getValue()); line = new YamlLine(":value"); assertFalse(line.isComment); assertEquals(0, line.indent); - assertEquals("", line.key); - assertEquals("value", line.value); + assertEquals("", line.getKey()); + assertEquals("value", line.getValue()); line = new YamlLine(" : value "); assertFalse(line.isComment); assertEquals(2, line.indent); - assertEquals("", line.key); - assertEquals("value", line.value); + assertEquals("", line.getKey()); + assertEquals("value", line.getValue()); line = new YamlLine("name : value "); assertFalse(line.isComment); assertEquals(0, line.indent); - assertEquals("name", line.key); - assertEquals("value", line.value); + assertEquals("name", line.getKey()); + assertEquals("value", line.getValue()); line = new YamlLine(" name : value "); assertFalse(line.isComment); assertEquals(2, line.indent); - assertEquals("name", line.key); - assertEquals("value", line.value); + assertEquals("name", line.getKey()); + assertEquals("value", line.getValue()); line = new YamlLine(" name : value ::"); assertFalse(line.isComment); assertEquals(2, line.indent); - assertEquals("name", line.key); - assertEquals("value", line.value); + assertEquals("name", line.getKey()); + assertEquals("value", line.getValue()); // split this gives parts.length = 0!! line = new YamlLine(":::"); assertFalse(line.isComment); assertEquals(0, line.indent); - assertEquals("", line.key); - assertEquals("", line.value); + assertEquals("", line.getKey()); + assertEquals("", line.getValue()); } @Test @@ -92,13 +92,13 @@ public void testItemLine() { YamlLine line = new YamlLine("- val1"); assertTrue(line.isItem); assertEquals(0, line.indent); - assertEquals("", line.key); - assertEquals("val1", line.value); + assertEquals("", line.getKey()); + assertEquals("val1", line.getValue()); line = new YamlLine(" - val1: ff"); assertTrue(line.isItem); assertEquals(2, line.indent); - assertEquals("", line.key); - assertEquals("val1: ff", line.value); + assertEquals("", line.getKey()); + assertEquals("val1: ff", line.getValue()); } } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java deleted file mode 100644 index 56e1cfd965..0000000000 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/yaml/MaliciousYamlTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * 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.yaml; - -import brut.androlib.ApkBuilder; -import brut.androlib.BaseTest; -import brut.androlib.TestUtils; -import brut.androlib.Config; -import brut.common.BrutException; -import brut.directory.ExtFile; -import brut.util.OS; -import org.junit.BeforeClass; -import org.junit.Test; -import org.yaml.snakeyaml.constructor.ConstructorException; - -import java.io.File; - -public class MaliciousYamlTest extends BaseTest { - - @BeforeClass - public static void beforeClass() throws Exception { - TestUtils.cleanFrameworkFile(); - - sTmpDir = new ExtFile(OS.createTempDirectory()); - sTestNewDir = new ExtFile(sTmpDir, "cve20220476"); - LOGGER.info("Unpacking cve20220476..."); - TestUtils.copyResourceDir(MaliciousYamlTest.class, "yaml/cve20220476/", sTestNewDir); - } - - //@Test(expected = ConstructorException.class) - @Test - public void testMaliciousYamlNotLoaded() throws BrutException { - Config config = Config.getDefaultConfig(); - File testApk = new File(sTmpDir, "cve20220476.apk"); - new ApkBuilder(config, sTestNewDir).build(testApk); - } -} diff --git a/brut.apktool/apktool-lib/src/test/resources/decode/yaml/basic/apktool.yml b/brut.apktool/apktool-lib/src/test/resources/apk/basic.yml similarity index 100% rename from brut.apktool/apktool-lib/src/test/resources/decode/yaml/basic/apktool.yml rename to brut.apktool/apktool-lib/src/test/resources/apk/basic.yml diff --git a/brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/apktool.yml b/brut.apktool/apktool-lib/src/test/resources/apk/cve20220476.yml similarity index 100% rename from brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/apktool.yml rename to brut.apktool/apktool-lib/src/test/resources/apk/cve20220476.yml diff --git a/brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/AndroidManifest.xml b/brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/AndroidManifest.xml deleted file mode 100644 index 11a1a7a8f7..0000000000 --- a/brut.apktool/apktool-lib/src/test/resources/yaml/cve20220476/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - From bda92f9323f0a89e67e5464ad69c094192390c9f Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Sat, 22 Jul 2023 12:19:40 +0300 Subject: [PATCH 3/7] remove dependencies from snakeyaml --- brut.apktool/apktool-cli/build.gradle | 1 - brut.apktool/apktool-lib/build.gradle | 1 - .../main/java/brut/androlib/apk/ApkInfo.java | 45 ------------- .../androlib/apk/ClassSafeConstructor.java | 63 ------------------- .../apk/EscapedStringRepresenter.java | 38 ----------- build.gradle | 1 - 6 files changed, 149 deletions(-) delete mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java delete mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java diff --git a/brut.apktool/apktool-cli/build.gradle b/brut.apktool/apktool-cli/build.gradle index c8ea405d53..5d8a7dd3f1 100644 --- a/brut.apktool/apktool-cli/build.gradle +++ b/brut.apktool/apktool-cli/build.gradle @@ -89,7 +89,6 @@ tasks.register('proguard', ProGuardTask) { dontwarn 'javax.xml.xpath.**' dontnote '**' // between Java 1.8 and 1.9, the signature of `flip()` changed, which trips up proguard. - dontwarn 'org.yaml.snakeyaml.scanner.ScannerImpl' def outPath = jar.getDestinationDirectory().getAsFile().get().toString() def extension = jar.archiveExtension.get().toString() diff --git a/brut.apktool/apktool-lib/build.gradle b/brut.apktool/apktool-lib/build.gradle index 080ffb7771..dcfa8ac5de 100644 --- a/brut.apktool/apktool-lib/build.gradle +++ b/brut.apktool/apktool-lib/build.gradle @@ -39,7 +39,6 @@ dependencies { implementation depends.baksmali implementation depends.smali - implementation depends.snakeyaml implementation depends.xmlpull implementation depends.guava implementation depends.commons_lang diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index 0a73b320f7..0d674ab05b 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -21,10 +21,6 @@ import brut.androlib.res.data.ResConfigFlags; import brut.directory.DirectoryException; import brut.directory.FileDirectory; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.introspector.PropertyUtils; import java.io.*; import java.util.ArrayList; @@ -54,26 +50,6 @@ public ApkInfo() { this.version = ApktoolProperties.getVersion(); } - private static Yaml getYaml() { - DumperOptions dumpOptions = new DumperOptions(); - dumpOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - EscapedStringRepresenter representer = new EscapedStringRepresenter(); - PropertyUtils propertyUtils = representer.getPropertyUtils(); - propertyUtils.setSkipMissingProperties(true); - - LoaderOptions loaderOptions = new LoaderOptions(); - loaderOptions.setCodePointLimit(10 * 1024 * 1024); // 10mb - - return new Yaml(new ClassSafeConstructor(), representer, dumpOptions, loaderOptions); - } - - public void save(Writer output) { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - getYaml().dump(this, output); - } - public String checkTargetSdkVersionBounds() { int target = mapSdkShorthandToVersion(getTargetSdkVersion()); @@ -157,16 +133,6 @@ private int mapSdkShorthandToVersion(String sdkVersion) { } } -// public void save(File file) throws IOException { -// try( -// FileOutputStream fos = new FileOutputStream(file); -// OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8); -// Writer writer = new BufferedWriter(outputStreamWriter) -// ) { -// save(writer); -// } -// } - public void save(File file) throws AndrolibException { try ( YamlWriter writer = new YamlWriter(new FileOutputStream(file)); @@ -187,17 +153,6 @@ public static ApkInfo load(InputStream is) throws AndrolibException { return apkInfo; } -// public static ApkInfo load(File appDir) -// throws AndrolibException { -// try( -// InputStream in = new FileDirectory(appDir).getFileInput("apktool.yml") -// ) { -// return ApkInfo.load(in); -// } catch (DirectoryException | IOException ex) { -// throw new AndrolibException(ex); -// } -// } - public static ApkInfo load(File appDir) throws AndrolibException { try( diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java deleted file mode 100644 index d1849ec05b..0000000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * 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.apk; - -import org.yaml.snakeyaml.constructor.AbstractConstruct; -import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.error.YAMLException; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.nodes.ScalarNode; -import org.yaml.snakeyaml.nodes.Tag; -import java.util.ArrayList; -import java.util.List; - -public class ClassSafeConstructor extends Constructor { - protected final List> allowableClasses = new ArrayList<>(); - - public ClassSafeConstructor() { - super(new LoaderOptions()); - this.yamlConstructors.put(Tag.STR, new ConstructStringEx()); - - this.allowableClasses.add(ApkInfo.class); - this.allowableClasses.add(PackageInfo.class); - this.allowableClasses.add(UsesFramework.class); - this.allowableClasses.add(VersionInfo.class); - } - - protected Object newInstance(Node node) { - if (this.yamlConstructors.containsKey(node.getTag()) || this.allowableClasses.contains(node.getType())) { - return super.newInstance(node); - } - throw new YAMLException("Invalid Class attempting to be constructed: " + node.getTag()); - } - - protected Object finalizeConstruction(Node node, Object data) { - if (this.yamlConstructors.containsKey(node.getTag()) || this.allowableClasses.contains(node.getType())) { - return super.finalizeConstruction(node, data); - } - - return this.newInstance(node); - } - - private class ConstructStringEx extends AbstractConstruct { - public Object construct(Node node) { - String val = constructScalar((ScalarNode) node); - return YamlStringEscapeUtils.unescapeString(val); - } - } -} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java deleted file mode 100644 index c2b13f9e00..0000000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * 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.apk; - -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.representer.Representer; - -public class EscapedStringRepresenter extends Representer { - public EscapedStringRepresenter() { - super(new DumperOptions()); - RepresentStringEx representStringEx = new RepresentStringEx(); - multiRepresenters.put(String.class, representStringEx); - representers.put(String.class, representStringEx); - } - - private class RepresentStringEx extends RepresentString { - - @Override - public Node representData(Object data) { - return super.representData(YamlStringEscapeUtils.escapeString(data.toString())); - } - } -} diff --git a/build.gradle b/build.gradle index 5858e2d9aa..6e6f443f2a 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,6 @@ buildscript { guava : 'com.google.guava:guava:32.0.1-jre', junit : 'junit:junit:4.13.2', proguard_gradle: 'com.guardsquare:proguard-gradle:7.3.2', - snakeyaml : 'org.yaml:snakeyaml:1.32:android', smali : 'com.android.tools.smali:smali:3.0.3', xmlpull : 'xpp3:xpp3:1.1.4c', xmlunit : 'xmlunit:xmlunit:1.6', From fc753b71abcac5f3af16afb4fcedb78789755caa Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Sun, 23 Jul 2023 11:01:23 +0300 Subject: [PATCH 4/7] remove unused methods --- .../main/java/brut/androlib/apk/YamlLine.java | 4 ++-- .../java/brut/androlib/apk/YamlReader.java | 4 ---- .../java/brut/androlib/apk/YamlWriter.java | 22 ------------------- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java index 07bce3ef75..5ecd86d548 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java @@ -66,7 +66,7 @@ public static String unescape(String value) { public String getValue() { if (value.equals("null")) return null; - String res = YamlStringEscapeUtils.unescapeString(value); + String res = unescape(value); // remove quotation marks res = res.replaceAll("^\"|\"$", ""); res = res.replaceAll("^'|'$", ""); @@ -74,7 +74,7 @@ public String getValue() { } public String getKey() { - String res = YamlStringEscapeUtils.unescapeString(key); + String res = unescape(key); // remove quotation marks res = res.replaceAll("^\"|\"$", ""); res = res.replaceAll("^'|'$", ""); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java index 2222dff412..be7d2cefca 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java @@ -16,10 +16,6 @@ public YamlReader(InputStream in) { read(in); } - public int getLineNo() { - return mCurrent + 1; - } - public void pushLine() { if (mCurrent > 0) mCurrent--; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java index bb6629b28e..7cc921641a 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java @@ -20,10 +20,6 @@ public void close() throws IOException { mWriter.close(); } - public int getIndent() { - return mIndent; - } - public String getIndentString() { // for java 11 // return " ".repeat(mIndent); @@ -48,11 +44,6 @@ public void writeIndent() { mWriter.print(getIndentString()); } - public void writeInt(String key, int value) { - writeIndent(); - mWriter.println(escape(key) + ": " + value); - } - public void writeBool(String key, boolean value) { writeIndent(); String val = value ? "true": "false"; @@ -85,19 +76,6 @@ public void writeList(String key, List list) { } } - public void writeCommonMap(String key, Map map) { - if (Objects.isNull(map)) - return; - writeIndent(); - mWriter.println(escape(key) + ":"); - nextIndent(); - for (K mapKey: map.keySet()) { - writeIndent(); - mWriter.println(mapKey + ": " + map.get(mapKey)); - } - prevIndent(); - } - public void writeStringMap(String key, Map map) { if (Objects.isNull(map)) return; From 5ba0fe4ce76abdd2f437250ddfef836690bc000e Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Sun, 23 Jul 2023 16:05:29 +0300 Subject: [PATCH 5/7] correct indent test value --- .../src/main/java/brut/androlib/apk/YamlReader.java | 1 - .../src/test/java/brut/androlib/apk/ApkInfoReaderTest.java | 4 ++-- .../src/test/resources/apk/first_incorrect_indent.yml | 2 +- .../src/test/resources/apk/skip_incorrect_indent.yml | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java index be7d2cefca..79a3254f95 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java @@ -62,7 +62,6 @@ public boolean nextLine() { return false; while (true) { mCurrent++; - // skip comments if (isCommentOrEmpty()) continue; return !isEnd(); diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java index 9896ed69b4..419b7a3d70 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/ApkInfoReaderTest.java @@ -53,7 +53,7 @@ public void testSkipIncorrectIndent() throws AndrolibException { ApkInfo apkInfo = ApkInfo.load( this.getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml")); checkStandard(apkInfo); - assertNotEquals("2.8.1", apkInfo.version); + assertNotEquals("2.0.0", apkInfo.version); } @Test @@ -61,7 +61,7 @@ public void testFirstIncorrectIndent() throws AndrolibException { ApkInfo apkInfo = ApkInfo.load( this.getClass().getResourceAsStream("/apk/first_incorrect_indent.yml")); checkStandard(apkInfo); - assertNotEquals("2.8.1", apkInfo.version); + assertNotEquals("2.0.0", apkInfo.version); } @Test diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml b/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml index 373b5fdfa2..49e9b66901 100644 --- a/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml +++ b/brut.apktool/apktool-lib/src/test/resources/apk/first_incorrect_indent.yml @@ -1,5 +1,5 @@ !!brut.androlib.meta.MetaInfo - version: 2.8.1 + version: 2.0.0 apkFileName: standard.apk compressionType: false doNotCompress: diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml b/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml index 3ff6a7be8b..cfdc2d7838 100644 --- a/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml +++ b/brut.apktool/apktool-lib/src/test/resources/apk/skip_incorrect_indent.yml @@ -1,6 +1,6 @@ !!brut.androlib.meta.MetaInfo apkFileName: standard.apk - version: 2.8.1 + version: 2.0.0 compressionType: false doNotCompress: - resources.arsc From 143cff8d462ee461ffcfc18633dffad512dac0b9 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 24 Jul 2023 10:35:25 +0300 Subject: [PATCH 6/7] correct style with curly braces --- .../main/java/brut/androlib/apk/YamlLine.java | 3 +- .../java/brut/androlib/apk/YamlReader.java | 33 ++++++++++++------- .../java/brut/androlib/apk/YamlWriter.java | 15 ++++++--- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java index 5ecd86d548..f31dd62ac8 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlLine.java @@ -64,8 +64,9 @@ public static String unescape(String value) { } public String getValue() { - if (value.equals("null")) + if (value.equals("null")) { return null; + } String res = unescape(value); // remove quotation marks res = res.replaceAll("^\"|\"$", ""); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java index 79a3254f95..a75bd8efa9 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlReader.java @@ -17,8 +17,9 @@ public YamlReader(InputStream in) { } public void pushLine() { - if (mCurrent > 0) + if (mCurrent > 0) { mCurrent--; + } } public void read(InputStream in) { @@ -48,22 +49,26 @@ public boolean isCommentOrEmpty() { } public void skipInsignificant() { - if (isEnd()) + if (isEnd()) { return; + } while (isCommentOrEmpty()) { mCurrent++; - if (isEnd()) + if (isEnd()) { break; + } } } public boolean nextLine() { - if (isEnd()) + if (isEnd()) { return false; + } while (true) { mCurrent++; - if (isCommentOrEmpty()) + if (isCommentOrEmpty()) { continue; + } return !isEnd(); } } @@ -80,13 +85,15 @@ interface Updater { * Read root object from start to end */ public void readRoot(T obj) throws AndrolibException { - if (isEnd()) + if (isEnd()) { return; + } int objIndent = 0; skipInsignificant(); while (true) { - if (isEnd()) + if (isEnd()) { return; + } YamlLine line = getLine(); // skip don't checked line or lines with other indent if (objIndent != line.indent || !line.hasColon) { @@ -106,8 +113,9 @@ public void readRoot(T obj) throws AndrolibExceptio public void readObject(T obj, Checker check, Updater updater) throws AndrolibException { - if (isEnd()) + if (isEnd()) { return; + } int prevIndent = getIndent(); // detect indent for the object data nextLine(); @@ -121,8 +129,9 @@ public void readObject(T obj, } updater.update(obj, this); while (nextLine()) { - if (isEnd()) + if (isEnd()) { return; + } line = getLine(); if (objIndent != line.indent || !check.check(line)) { pushLine(); @@ -145,14 +154,16 @@ void readObject(T obj) throws AndrolibException { */ public void readList(List list, Updater> updater) throws AndrolibException { - if (isEnd()) + if (isEnd()) { return; + } int listIndent = getIndent(); nextLine(); int dataIndent = getIndent(); while (true) { - if (isEnd()) + if (isEnd()) { return; + } // check incorrect data indent if (dataIndent < listIndent) { pushLine(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java index 7cc921641a..6750212b39 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlWriter.java @@ -36,8 +36,9 @@ public void nextIndent() { } public void prevIndent() { - if (mIndent != 0) + if (mIndent != 0) { mIndent -= 2; + } } public void writeIndent() { @@ -55,8 +56,9 @@ public void writeString(String key, String value, boolean quoted) { if (Objects.isNull(value)) { mWriter.println(escape(key) + ": null"); } else { - if (quoted) + if (quoted) { value = QUOTE + value + QUOTE; + } mWriter.println(escape(key) + ": " + escape(value)); } } @@ -66,8 +68,9 @@ public void writeString(String key, String value) { } public void writeList(String key, List list) { - if (Objects.isNull(list)) + if (Objects.isNull(list)) { return; + } writeIndent(); mWriter.println(escape(key) + ":"); for (T item: list) { @@ -77,8 +80,9 @@ public void writeList(String key, List list) { } public void writeStringMap(String key, Map map) { - if (Objects.isNull(map)) + if (Objects.isNull(map)) { return; + } writeIndent(); mWriter.println(escape(key) + ":"); nextIndent(); @@ -89,8 +93,9 @@ public void writeStringMap(String key, Map map) { } public void writeObject(String key, T obj) { - if (Objects.isNull(obj)) + if (Objects.isNull(obj)) { return; + } writeIndent(); mWriter.println(escape(key) + ":"); nextIndent(); From 859f5e394ea1a7183086c9fff66296a8e27c7c66 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 24 Jul 2023 14:02:26 +0300 Subject: [PATCH 7/7] add test item with hieroglyph --- .../apk/DoNotCompressHieroglyphTest.java | 20 +++++++++++++++++++ .../apk/donotcompress_with_hieroglyph.yml | 6 ++++++ 2 files changed, 26 insertions(+) create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/DoNotCompressHieroglyphTest.java create mode 100644 brut.apktool/apktool-lib/src/test/resources/apk/donotcompress_with_hieroglyph.yml diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/DoNotCompressHieroglyphTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/DoNotCompressHieroglyphTest.java new file mode 100644 index 0000000000..ff116eafa7 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/apk/DoNotCompressHieroglyphTest.java @@ -0,0 +1,20 @@ +package brut.androlib.apk; + +import brut.androlib.exceptions.AndrolibException; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DoNotCompressHieroglyphTest { + + @Test + public void testHieroglyph() throws AndrolibException { + ApkInfo apkInfo = ApkInfo.load( + this.getClass().getResourceAsStream("/apk/donotcompress_with_hieroglyph.yml")); + assertEquals("2.0.0", apkInfo.version); + assertEquals("testapp.apk", apkInfo.getApkFileName()); + assertEquals(2, apkInfo.doNotCompress.size()); + assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1", apkInfo.doNotCompress.get(0)); + assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1.manifest", apkInfo.doNotCompress.get(1)); + } +} diff --git a/brut.apktool/apktool-lib/src/test/resources/apk/donotcompress_with_hieroglyph.yml b/brut.apktool/apktool-lib/src/test/resources/apk/donotcompress_with_hieroglyph.yml new file mode 100644 index 0000000000..f3ff13dad6 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/apk/donotcompress_with_hieroglyph.yml @@ -0,0 +1,6 @@ +version: 2.0.0 +apkFileName: testapp.apk +doNotCompress: +- assets/AllAssetBundles/Andriod/tx_1001_冰原1 +- assets/AllAssetBundles/Andriod/tx_1001_冰原1.manifest +