Remove SnakeYAML for manual YAML Parser (#3191)

* Simple straitforward yaml serialization with minimal needed functionality

* Consolidate ApkInfo tests in the package brut.androlib.apk, unify interface YamlReader and add ApkInfoSerializationTest read -> write -> read test

* remove dependencies from snakeyaml

* remove unused methods

* correct indent test value

* correct style with curly braces

* add test item with hieroglyph
This commit is contained in:
sv99 2023-07-29 12:59:15 +03:00 committed by GitHub
parent da6ed0f729
commit 62b9eedb9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1084 additions and 208 deletions

View File

@ -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()

View File

@ -39,7 +39,6 @@ dependencies {
implementation depends.baksmali
implementation depends.smali
implementation depends.snakeyaml
implementation depends.xmlpull
implementation depends.guava
implementation depends.commons_lang

View File

@ -165,7 +165,7 @@ public class ApkDecoder {
copyRawFiles(outDir);
copyUnknownFiles(apkInfo, outDir);
Collection<String> mUncompressedFiles = new ArrayList<>();
List<String> mUncompressedFiles = new ArrayList<>();
recordUncompressedFiles(apkInfo, resourcesDecoder.getResFileMapping(), mUncompressedFiles);
copyOriginalFiles(outDir);
writeApkInfo(apkInfo, outDir);
@ -220,11 +220,7 @@ public class ApkDecoder {
}
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 @@ public class ApkDecoder {
private void recordUncompressedFiles(ApkInfo apkInfo,
Map<String, String> resFileMapping,
Collection<String> uncompressedFilesOrExts)
List<String> uncompressedFilesOrExts)
throws AndrolibException {
try {
Directory unk = mApkFile.getDirectory();

View File

@ -21,18 +21,14 @@ import brut.androlib.exceptions.AndrolibException;
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.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.ArrayList;
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 +41,7 @@ public class ApkInfo {
public boolean sharedLibrary;
public boolean sparseResources;
public Map<String, String> unknownFiles;
public Collection<String> doNotCompress;
public List<String> doNotCompress;
/** @deprecated use {@link #resourcesAreCompressed} */
public boolean compressionType;
@ -54,26 +50,6 @@ public class 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,28 +133,111 @@ public class ApkInfo {
}
}
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 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();
reader.readRoot(apkInfo);
return apkInfo;
}
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.getKey()) {
case "version": {
this.version = line.getValue();
break;
}
case "apkFileName": {
this.apkFileName = line.getValue();
break;
}
case "isFrameworkApk": {
this.isFrameworkApk = line.getValueBool();
break;
}
case "usesFramework": {
this.usesFramework = new UsesFramework();
reader.readObject(usesFramework);
break;
}
case "sdkInfo": {
reader.readMap(sdkInfo);
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 = new LinkedHashMap<>();
reader.readMap(unknownFiles);
break;
}
case "doNotCompress": {
this.doNotCompress = new ArrayList<>();
reader.readStringList(doNotCompress);
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);
}
}

View File

@ -1,63 +0,0 @@
/*
* 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.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<Class<?>> 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);
}
}
}

View File

@ -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.getKey()) {
case "forcedPackageId": {
forcedPackageId = line.getValue();
break;
}
case "renameManifestPackage": {
renameManifestPackage = line.getValue();
break;
}
}
}
@Override
public void write(YamlWriter writer) {
writer.writeString("forcedPackageId", forcedPackageId);
writer.writeString("renameManifestPackage", renameManifestPackage);
}
}

View File

@ -16,9 +16,34 @@
*/
package brut.androlib.apk;
import brut.androlib.exceptions.AndrolibException;
import java.util.ArrayList;
import java.util.List;
public class UsesFramework {
public class UsesFramework implements YamlSerializable {
public List<Integer> ids;
public String tag;
@Override
public void readItem(YamlReader reader) throws AndrolibException {
YamlLine line = reader.getLine();
switch (line.getKey()) {
case "ids": {
ids = new ArrayList<>();
reader.readIntList(ids);
break;
}
case "tag": {
tag = line.getValue();
break;
}
}
}
@Override
public void write(YamlWriter writer) {
writer.writeList("ids", ids);
writer.writeString("tag", tag);
}
}

View File

@ -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.getKey()) {
case "versionCode": {
versionCode = line.getValue();
break;
}
case "versionName": {
versionName = line.getValue();
break;
}
}
}
@Override
public void write(YamlWriter writer) {
writer.writeString("versionCode", versionCode);
writer.writeString("versionName", versionName);
}
}

View File

@ -0,0 +1,92 @@
package brut.androlib.apk;
import java.util.Objects;
public class YamlLine {
public int indent = 0;
private String key = "";
private 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 static String unescape(String value) {
return YamlStringEscapeUtils.unescapeString(value);
}
public String getValue() {
if (value.equals("null")) {
return null;
}
String res = unescape(value);
// remove quotation marks
res = res.replaceAll("^\"|\"$", "");
res = res.replaceAll("^'|'$", "");
return res;
}
public String getKey() {
String res = unescape(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);
}
}

View File

@ -0,0 +1,204 @@
package brut.androlib.apk;
import brut.androlib.exceptions.AndrolibException;
import java.io.InputStream;
import java.util.*;
public class YamlReader {
private ArrayList<YamlLine> mLines;
private int mCurrent = 0;
public YamlReader(InputStream in) {
mLines = new ArrayList<>();
mLines.add(new YamlLine(null));
read(in);
}
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++;
if (isCommentOrEmpty()) {
continue;
}
return !isEnd();
}
}
interface Checker {
boolean check(YamlLine line);
}
interface Updater<T> {
void update(T items, YamlReader reader) throws AndrolibException;
}
/**
* Read root object from start to end
*/
public <T extends YamlSerializable> void readRoot(T obj) throws AndrolibException {
if (isEnd()) {
return;
}
int objIndent = 0;
skipInsignificant();
while (true) {
if (isEnd()) {
return;
}
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> void readObject(T obj,
Checker check,
Updater<T> updater) throws AndrolibException {
if (isEnd()) {
return;
}
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;
}
updater.update(obj, this);
while (nextLine()) {
if (isEnd()) {
return;
}
line = getLine();
if (objIndent != line.indent || !check.check(line)) {
pushLine();
return;
}
updater.update(obj, this);
}
}
<T extends YamlSerializable> void readObject(T obj) throws AndrolibException {
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 <T> void readList(List<T> list,
Updater<List<T>> updater) throws AndrolibException {
if (isEnd()) {
return;
}
int listIndent = getIndent();
nextLine();
int dataIndent = getIndent();
while (true) {
if (isEnd()) {
return;
}
// check incorrect data indent
if (dataIndent < listIndent) {
pushLine();
return;
}
YamlLine line = getLine();
if (dataIndent != line.indent || !line.isItem) {
pushLine();
return;
}
updater.update(list, this);
nextLine();
}
}
public void readStringList(List<String> list) throws AndrolibException {
readList(list,
(items, reader) -> {
items.add(reader.getLine().getValue());
});
};
public void readIntList(List<Integer> list) throws AndrolibException {
readList(list,
(items, reader) -> {
items.add(reader.getLine().getValueInt());
});
};
public void readMap(Map<String, String> map) throws AndrolibException {
readObject(map,
line -> line.hasColon,
(items, reader) -> {
YamlLine line = reader.getLine();
items.put(line.getKey(), line.getValue());
});
};
}

View File

@ -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);
}

View File

@ -0,0 +1,105 @@
package brut.androlib.apk;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class YamlWriter implements Closeable {
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 IOException {
mWriter.close();
}
public String getIndentString() {
// for java 11
// return " ".repeat(mIndent);
// for java 8
return String.join("", Collections.nCopies(mIndent, " "));
}
public static String escape(String value) {
return YamlStringEscapeUtils.escapeString(value);
}
public void nextIndent() {
mIndent += 2;
}
public void prevIndent() {
if (mIndent != 0) {
mIndent -= 2;
}
}
public void writeIndent() {
mWriter.print(getIndentString());
}
public void writeBool(String key, boolean value) {
writeIndent();
String val = value ? "true": "false";
mWriter.println(escape(key) + ": " + val);
}
public void writeString(String key, String value, boolean quoted) {
writeIndent();
if (Objects.isNull(value)) {
mWriter.println(escape(key) + ": null");
} else {
if (quoted) {
value = QUOTE + value + QUOTE;
}
mWriter.println(escape(key) + ": " + escape(value));
}
}
public void writeString(String key, String value) {
writeString(key, value, false);
}
public <T> void writeList(String key, List<T> list) {
if (Objects.isNull(list)) {
return;
}
writeIndent();
mWriter.println(escape(key) + ":");
for (T item: list) {
writeIndent();
mWriter.println("- " + item);
}
}
public void writeStringMap(String key, Map<String, String> map) {
if (Objects.isNull(map)) {
return;
}
writeIndent();
mWriter.println(escape(key) + ":");
nextIndent();
for (String mapKey: map.keySet()) {
writeString(mapKey, map.get(mapKey));
}
prevIndent();
}
public <T extends YamlSerializable> void writeObject(String key, T obj) {
if (Objects.isNull(obj)) {
return;
}
writeIndent();
mWriter.println(escape(key) + ":");
nextIndent();
obj.write(this);
prevIndent();
}
}

View File

@ -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.0.0", apkInfo.version);
}
@Test
public void testFirstIncorrectIndent() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
checkStandard(apkInfo);
assertNotEquals("2.0.0", 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));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.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"));
}
}

View File

@ -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());

View File

@ -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));
}
}

View File

@ -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() {

View File

@ -16,23 +16,17 @@
*/
package brut.androlib.apk;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Representer;
import brut.androlib.exceptions.AndrolibException;
import org.junit.Test;
public class EscapedStringRepresenter extends Representer {
public EscapedStringRepresenter() {
super(new DumperOptions());
RepresentStringEx representStringEx = new RepresentStringEx();
multiRepresenters.put(String.class, representStringEx);
representers.put(String.class, representStringEx);
}
import static org.junit.Assert.assertEquals;
private class RepresentStringEx extends RepresentString {
public class MaliciousYamlTest {
@Override
public Node representData(Object data) {
return super.representData(YamlStringEscapeUtils.escapeString(data.toString()));
}
@Test
public void testMaliciousYaml() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/cve20220476.yml"));
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version);
}
}

View File

@ -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.getKey());
assertEquals("This is comment", line.getValue());
line = new YamlLine(" # This is comment");
assertEquals(2, line.indent);
assertTrue(line.isComment);
assertEquals("", line.getKey());
assertEquals("This is comment", line.getValue());
}
@Test
public void testKeyLine() {
YamlLine line = new YamlLine("name:");
assertFalse(line.isComment);
assertEquals(0, line.indent);
assertEquals("name", line.getKey());
assertEquals("", line.getValue());
line = new YamlLine(" name:");
assertFalse(line.isComment);
assertEquals(2, line.indent);
assertEquals("name", line.getKey());
assertEquals("", line.getValue());
line = new YamlLine(":value");
assertFalse(line.isComment);
assertEquals(0, line.indent);
assertEquals("", line.getKey());
assertEquals("value", line.getValue());
line = new YamlLine(" : value ");
assertFalse(line.isComment);
assertEquals(2, line.indent);
assertEquals("", line.getKey());
assertEquals("value", line.getValue());
line = new YamlLine("name : value ");
assertFalse(line.isComment);
assertEquals(0, line.indent);
assertEquals("name", line.getKey());
assertEquals("value", line.getValue());
line = new YamlLine(" name : value ");
assertFalse(line.isComment);
assertEquals(2, line.indent);
assertEquals("name", line.getKey());
assertEquals("value", line.getValue());
line = new YamlLine(" name : value ::");
assertFalse(line.isComment);
assertEquals(2, line.indent);
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.getKey());
assertEquals("", line.getValue());
}
@Test
public void testItemLine() {
YamlLine line = new YamlLine("- val1");
assertTrue(line.isItem);
assertEquals(0, line.indent);
assertEquals("", line.getKey());
assertEquals("val1", line.getValue());
line = new YamlLine(" - val1: ff");
assertTrue(line.isItem);
assertEquals(2, line.indent);
assertEquals("", line.getKey());
assertEquals("val1: ff", line.getValue());
}
}

View File

@ -1,50 +0,0 @@
/*
* 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.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)
public void testMaliciousYamlNotLoaded() throws BrutException {
Config config = Config.getDefaultConfig();
File testApk = new File(sTmpDir, "cve20220476.apk");
new ApkBuilder(config, sTestNewDir).build(testApk);
}
}

View File

@ -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

View File

@ -0,0 +1,22 @@
!!brut.androlib.meta.MetaInfo
version: 2.0.0
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

View File

@ -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

View File

@ -0,0 +1,22 @@
!!brut.androlib.meta.MetaInfo
apkFileName: standard.apk
version: 2.0.0
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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="30" android:compileSdkVersionCodename="11" package="com.ibotpeaches.cve20220476" platformBuildVersionCode="30" platformBuildVersionName="11">
<application android:debuggable="true" android:forceQueryable="true">
</application>
</manifest>

View File

@ -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',