core: option for control escaping of unicode characters (#103)
This commit is contained in:
@@ -30,4 +30,6 @@ public interface IJadxArgs {
|
||||
boolean isDeobfuscationForceSave();
|
||||
|
||||
boolean useSourceNameAsClassAlias();
|
||||
|
||||
boolean escapeUnicode();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public class JadxArgs implements IJadxArgs {
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outDir;
|
||||
@@ -149,4 +151,13 @@ public class JadxArgs implements IJadxArgs {
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +130,11 @@ public class AnnotationGen {
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
code.add(getStringUtils().unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(StringUtils.unescapeChar((Character) val));
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
@@ -172,4 +172,8 @@ public class AnnotationGen {
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,7 +348,7 @@ public class ClassGen {
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls));
|
||||
} else {
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
|
||||
@@ -38,7 +38,6 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -130,8 +129,8 @@ public class InsnGen {
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
}
|
||||
|
||||
private static String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
@@ -236,7 +235,7 @@ public class InsnGen {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(StringUtils.unescapeString(str));
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
|
||||
@@ -255,7 +255,7 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -31,7 +33,16 @@ public class TypeGen {
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
return literalToString(lit, type, new StringUtils(new JadxArgs()));
|
||||
}
|
||||
|
||||
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
@@ -46,7 +57,7 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
return StringUtils.unescapeChar((char) lit);
|
||||
return stringUtils.unescapeChar((char) lit);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
case SHORT:
|
||||
|
||||
@@ -43,7 +43,7 @@ import com.android.dex.ClassData.Method;
|
||||
import com.android.dex.ClassDef;
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final DexNode dex;
|
||||
@@ -472,10 +472,16 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return dex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex.root();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return clsInfo.getRawName();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
import com.android.dex.TypeList;
|
||||
|
||||
public class DexNode {
|
||||
public class DexNode implements IDexNode {
|
||||
|
||||
public static final int NO_INDEX = -1;
|
||||
|
||||
@@ -210,10 +210,16 @@ public class DexNode {
|
||||
return dexBuf.open(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DEX";
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
public interface IDexNode {
|
||||
|
||||
DexNode dex();
|
||||
|
||||
RootNode root();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import com.android.dex.Code;
|
||||
import com.android.dex.Code.CatchHandler;
|
||||
import com.android.dex.Code.Try;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@@ -594,10 +594,16 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return parentClass.dex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex().root();
|
||||
}
|
||||
|
||||
public MethodInfo getMethodInfo() {
|
||||
return mthInfo;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import jadx.api.ResourcesLoader;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
@@ -31,6 +32,7 @@ public class RootNode {
|
||||
|
||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||
private final IJadxArgs args;
|
||||
private final StringUtils stringUtils;
|
||||
|
||||
private List<DexNode> dexNodes;
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
@@ -41,6 +43,7 @@ public class RootNode {
|
||||
|
||||
public RootNode(IJadxArgs args) {
|
||||
this.args = args;
|
||||
this.stringUtils = new StringUtils(args);
|
||||
}
|
||||
|
||||
public void load(List<InputFile> inputFiles) throws DecodeException {
|
||||
@@ -194,4 +197,8 @@ public class RootNode {
|
||||
public IJadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public StringUtils getStringUtils() {
|
||||
return stringUtils;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
private final boolean escapeUnicode;
|
||||
|
||||
public StringUtils(IJadxArgs args) {
|
||||
this.escapeUnicode = args.escapeUnicode();
|
||||
}
|
||||
|
||||
public static String unescapeString(String str) {
|
||||
public String unescapeString(String str) {
|
||||
int len = str.length();
|
||||
if (len == 0) {
|
||||
return "\"\"";
|
||||
}
|
||||
StringBuilder res = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
int c = str.charAt(i) & 0xFFFF;
|
||||
processChar(c, res);
|
||||
@@ -16,7 +23,7 @@ public class StringUtils {
|
||||
return '"' + res.toString() + '"';
|
||||
}
|
||||
|
||||
public static String unescapeChar(char ch) {
|
||||
public String unescapeChar(char ch) {
|
||||
if (ch == '\'') {
|
||||
return "'\\\''";
|
||||
}
|
||||
@@ -25,7 +32,7 @@ public class StringUtils {
|
||||
return '\'' + res.toString() + '\'';
|
||||
}
|
||||
|
||||
private static void processChar(int c, StringBuilder res) {
|
||||
private void processChar(int c, StringBuilder res) {
|
||||
switch (c) {
|
||||
case '\n': res.append("\\n"); break;
|
||||
case '\r': res.append("\\r"); break;
|
||||
@@ -37,10 +44,10 @@ public class StringUtils {
|
||||
case '\\': res.append("\\\\"); break;
|
||||
|
||||
default:
|
||||
if (32 <= c && c <= 126) {
|
||||
res.append((char) c);
|
||||
} else {
|
||||
if (c < 32 || c >= 127 && escapeUnicode) {
|
||||
res.append("\\u").append(String.format("%04x", c));
|
||||
} else {
|
||||
res.append((char) c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package jadx.tests
|
||||
|
||||
import jadx.api.JadxArgs
|
||||
import jadx.core.utils.StringUtils
|
||||
import spock.lang.Specification
|
||||
|
||||
class TestStringUtils extends Specification {
|
||||
|
||||
def "unescape string"() {
|
||||
def args = new JadxArgs()
|
||||
args.setEscapeUnicode(true)
|
||||
def stringUtils = new StringUtils(args)
|
||||
expect:
|
||||
StringUtils.unescapeString(input) == "\"$expected\""
|
||||
stringUtils.unescapeString(input) == "\"$expected\""
|
||||
|
||||
where:
|
||||
input | expected
|
||||
@@ -26,12 +30,14 @@ class TestStringUtils extends Specification {
|
||||
|
||||
def "unescape char"() {
|
||||
expect:
|
||||
StringUtils.unescapeChar(input as char) == "'$expected'"
|
||||
new StringUtils(new JadxArgs()).unescapeChar(input as char) == "'$expected'"
|
||||
|
||||
where:
|
||||
input | expected
|
||||
'a' | "a"
|
||||
' ' | " "
|
||||
'\n' | "\\n"
|
||||
'\'' | "\\\'"
|
||||
'\0' | "\\u0000"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user