fix(res): support formatted=false for strings (PR #1947)

This commit is contained in:
nitram84
2023-07-09 21:05:29 +02:00
committed by GitHub
parent d076c4e73d
commit ce9645864d
3 changed files with 131 additions and 0 deletions
@@ -250,6 +250,11 @@ public class ResXmlGen {
cw.add(' ').add(attrName).add("=\"").add(attrValue).add('"');
}
}
if (itemTag.equals("string") && valueStr.contains("%") && StringFormattedCheck.hasMultipleNonPositionalSubstitutions(valueStr)) {
cw.add(" formatted=\"false\"");
}
if (valueStr.equals("")) {
cw.add(" />");
} else {
@@ -0,0 +1,104 @@
package jadx.core.xmlgen;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/*
* This class contains source code form https://github.com/iBotPeaches/Apktool/
* see:
* https://github.com/iBotPeaches/Apktool/blob/master/brut.apktool/apktool-lib/src/main/java/brut/
* androlib/res/xml/ResXmlEncoders.java
*/
public class StringFormattedCheck {
public static boolean hasMultipleNonPositionalSubstitutions(String str) {
Duo<List<Integer>, List<Integer>> tuple = findSubstitutions(str, 4);
return !tuple.m1.isEmpty() && tuple.m1.size() + tuple.m2.size() > 1;
}
@SuppressWarnings("checkstyle:ClassTypeParameterName")
private static class Duo<T1, T2> {
public final T1 m1;
public final T2 m2;
public Duo(T1 t1, T2 t2) {
this.m1 = t1;
this.m2 = t2;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
final Duo<T1, T2> other = (Duo<T1, T2>) obj;
if (!Objects.equals(this.m1, other.m1)) {
return false;
}
return Objects.equals(this.m2, other.m2);
}
@Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + (this.m1 != null ? this.m1.hashCode() : 0);
hash = 71 * hash + (this.m2 != null ? this.m2.hashCode() : 0);
return hash;
}
}
/**
* It returns a tuple of:
* - a list of offsets of non positional substitutions. non-pos is defined as any "%" which isn't
* "%%" nor "%\d+\$"
* - a list of offsets of positional substitutions
*/
@SuppressWarnings({ "checkstyle:NeedBraces", "checkstyle:EmptyStatement" })
private static Duo<List<Integer>, List<Integer>> findSubstitutions(String str, int nonPosMax) {
if (nonPosMax == -1) {
nonPosMax = Integer.MAX_VALUE;
}
int pos;
int pos2 = 0;
List<Integer> nonPositional = new ArrayList<>();
List<Integer> positional = new ArrayList<>();
if (str == null) {
return new Duo<>(nonPositional, positional);
}
int length = str.length();
while ((pos = str.indexOf('%', pos2)) != -1) {
pos2 = pos + 1;
if (pos2 == length) {
nonPositional.add(pos);
break;
}
char c = str.charAt(pos2++);
if (c == '%') {
continue;
}
if (c >= '0' && c <= '9' && pos2 < length) {
while ((c = str.charAt(pos2++)) >= '0' && c <= '9' && pos2 < length)
;
if (c == '$') {
positional.add(pos);
continue;
}
}
nonPositional.add(pos);
if (nonPositional.size() >= nonPosMax) {
break;
}
}
return new Duo<>(nonPositional, positional);
}
}
@@ -149,6 +149,28 @@ class ResXmlGenTest {
+ "</resources>", files.get(0).getText().toString());
}
@Test
void testStringFormattedFalse() {
ResourceStorage resStorage = new ResourceStorage();
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "string", "app_name", "");
re.setSimpleValue(new RawValue(3, 0));
re.setNamedValues(Lists.list());
resStorage.add(re);
BinaryXMLStrings strings = new BinaryXMLStrings();
strings.put(0, "%s at %s");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
List<ResContainer> files = resXmlGen.makeResourcesXml();
assertEquals(1, files.size());
assertEquals("res/values/strings.xml", files.get(0).getFileName());
assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<resources>\n"
+ " <string name=\"app_name\" formatted=\"false\">%s at %s</string>\n"
+ "</resources>", files.get(0).getText().toString());
}
@Test
void testArrayEscape() {
ResourceStorage resStorage = new ResourceStorage();