fix XML encode errors

This commit is contained in:
REAndroid 2023-01-04 10:51:58 -05:00
parent 1b9ed9c291
commit 7389358a18
18 changed files with 220 additions and 129 deletions

View File

@ -42,9 +42,11 @@ import java.util.*;
private final ApkModule apkModule;
private final Map<Integer, Set<ResConfig>> decodedEntries;
private XMLBagDecoder xmlBagDecoder;
private final Set<String> mDecodedPaths;
public ApkModuleXmlDecoder(ApkModule apkModule){
this.apkModule=apkModule;
this.decodedEntries = new HashMap<>();
this.mDecodedPaths = new HashSet<>();
}
public void decodeTo(File outDir)
throws IOException, XMLException {
@ -60,6 +62,8 @@ import java.util.*;
decodeAndroidManifest(entryStore, outDir);
addDecodedPath(TableBlock.FILE_NAME);
logMessage("Decoding resource files ...");
List<ResFile> resFileList=apkModule.listResFiles();
for(ResFile resFile:resFileList){
@ -76,6 +80,7 @@ import java.util.*;
}else {
decodeResRaw(outDir, resFile);
}
addDecodedPath(resFile.getFilePath());
}
private void decodeResRaw(File outDir, ResFile resFile)
throws IOException {
@ -147,6 +152,7 @@ import java.util.*;
int currentPackageId= manifestBlock.guessCurrentPackageId();
XMLDocument xmlDocument=manifestBlock.decodeToXml(entryStore, currentPackageId);
xmlDocument.save(file, true);
addDecodedPath(AndroidManifestBlock.FILE_NAME);
}
private void addDecodedEntry(Collection<EntryBlock> entryBlockList){
for(EntryBlock entryBlock:entryBlockList){
@ -245,7 +251,11 @@ import java.util.*;
logMessage("Extracting root files");
File rootDir = new File(outDir, "root");
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
if(containsDecodedPath(inputSource.getAlias())){
continue;
}
extractRootFiles(rootDir, inputSource);
addDecodedPath(inputSource.getAlias());
}
}
private void extractRootFiles(File rootDir, InputSource inputSource) throws IOException {
@ -260,6 +270,12 @@ import java.util.*;
inputSource.write(outputStream);
outputStream.close();
}
private boolean containsDecodedPath(String path){
return mDecodedPaths.contains(path);
}
private void addDecodedPath(String path){
mDecodedPaths.add(path);
}
private void logMessage(String msg) {
APKLogger apkLogger=apkModule.getApkLogger();

View File

@ -49,7 +49,7 @@ public class ApkUtil {
public static List<File> recursiveFiles(File dir, String ext){
List<File> results=new ArrayList<>();
if(dir.isFile()){
if(ext==null || dir.getName().endsWith(ext)){
if(hasExtension(dir, ext)){
results.add(dir);
}
return results;
@ -63,7 +63,7 @@ public class ApkUtil {
}
for(File file:files){
if(file.isFile()){
if(ext!=null && !file.getName().endsWith(ext)){
if(!hasExtension(file, ext)){
continue;
}
results.add(file);
@ -74,26 +74,7 @@ public class ApkUtil {
return results;
}
public static List<File> recursiveFiles(File dir){
List<File> results=new ArrayList<>();
if(dir.isFile()){
results.add(dir);
return results;
}
if(!dir.isDirectory()){
return results;
}
File[] files=dir.listFiles();
if(files==null){
return results;
}
for(File file:files){
if(file.isFile()){
results.add(file);
continue;
}
results.addAll(recursiveFiles(file));
}
return results;
return recursiveFiles(dir, null);
}
public static List<File> listDirectories(File dir){
List<File> results=new ArrayList<>();
@ -116,7 +97,7 @@ public class ApkUtil {
}
for(File file:files){
if(file.isFile()){
if(ext!=null && !file.getName().endsWith(ext)){
if(!hasExtension(file, ext)){
continue;
}
results.add(file);
@ -124,6 +105,14 @@ public class ApkUtil {
}
return results;
}
private static boolean hasExtension(File file, String ext){
if(ext==null){
return true;
}
String name=file.getName().toLowerCase();
ext=ext.toLowerCase();
return name.endsWith(ext);
}
public static String toModuleName(File file){
String name=file.getName();
int i=name.lastIndexOf('.');

View File

@ -39,6 +39,7 @@
private PackageBlock currentPackage;
private final Set<FrameworkTable> frameworkTables = new HashSet<>();
private APKLogger apkLogger;
private boolean mForceCreateNamespaces = true;
public EncodeMaterials(){
}
public SpecString getSpecString(String name){
@ -278,6 +279,11 @@
}
return null;
}
public EncodeMaterials setForceCreateNamespaces(boolean force) {
this.mForceCreateNamespaces = force;
return this;
}
public EncodeMaterials setPackageIds(ResourceIds.Table.Package packageIds) {
this.packageIds = packageIds;
return this;
@ -302,6 +308,10 @@
public PackageBlock getCurrentPackage() {
return currentPackage;
}
public boolean isForceCreateNamespaces() {
return mForceCreateNamespaces;
}
public String getCurrentPackageName(){
return currentPackage.getName();
}

View File

@ -113,4 +113,9 @@ package com.reandroid.lib.apk.xmlencoder;
}
public static final String NULL_PACKAGE_NAME = "NULL_PACKAGE_NAME";
private static final Pattern PATTERN_TYPE=Pattern.compile("^([a-z]+)[^a-z]*.*$");
public static final String URI_ANDROID = "http://schemas.android.com/apk/res/android";
public static final String URI_APP = "http://schemas.android.com/apk/res-auto";
public static final String PREFIX_ANDROID = "android";
public static final String PREFIX_APP = "app";
}

View File

@ -63,10 +63,12 @@ public class XMLEncodeSource extends ByteInputSource {
}
try {
XMLFileEncoder xmlFileEncoder=new XMLFileEncoder(encodeMaterials);
xmlFileEncoder.setCurrentPath(xmlSource.getPath());
encodeMaterials.logVerbose("Encoding xml: "+xmlSource.getPath());
resXmlBlock = xmlFileEncoder.encode(xmlSource.getXMLDocument());
} catch (XMLException ex) {
throw new IOException(ex.getMessage(), ex);
throw new EncodeException("XMLException on: '"+xmlSource.getPath()
+"'\n '"+ex.getMessage()+"'");
}
return resXmlBlock;
}

View File

@ -30,9 +30,15 @@ import java.io.InputStream;
public class XMLFileEncoder {
private final EncodeMaterials materials;
private ResXmlBlock resXmlBlock;
private String mCurrentPath;
public XMLFileEncoder(EncodeMaterials materials){
this.materials=materials;
}
// Just for logging purpose
public void setCurrentPath(String path) {
this.mCurrentPath = path;
}
public ResXmlBlock encode(String xmlString){
try {
return encode(XMLDocument.load(xmlString));
@ -50,6 +56,7 @@ public class XMLFileEncoder {
return null;
}
public ResXmlBlock encode(File xmlFile){
setCurrentPath(xmlFile.getAbsolutePath());
try {
return encode(XMLDocument.load(xmlFile));
} catch (XMLException ex) {
@ -100,6 +107,14 @@ public class XMLFileEncoder {
String prefix=attribute.getNamePrefix();
if(prefix!=null){
ResXmlStartNamespace ns = resXmlElement.getStartNamespaceByPrefix(prefix);
if(ns==null){
ns=forceCreateNamespace(resXmlElement, resourceId, prefix);
}
if(ns==null){
throw new EncodeException("Namespace not found: "
+attribute.toString()
+", path="+mCurrentPath);
}
xmlAttribute.setNamespaceReference(ns.getUriReference());
}
@ -125,15 +140,14 @@ public class XMLFileEncoder {
xmlAttribute.setRawValue(encodeResult.value);
continue;
}
if(attributeBag.contains(AttributeValueType.STRING)) {
if(attributeBag.isEqualType(AttributeValueType.STRING)) {
xmlAttribute.setValueAsString(valueText);
continue;
}
}
if(EncodeUtil.isEmpty(valueText)) {
xmlAttribute.setValueType(ValueType.NULL);
xmlAttribute.setRawValue(0);
xmlAttribute.setValueAsString("");
}else{
ValueDecoder.EncodeResult encodeResult =
ValueDecoder.encodeGuessAny(valueText);
@ -145,6 +159,7 @@ public class XMLFileEncoder {
}
}
}
resXmlElement.calculatePositions();
}
private void ensureNamespaces(XMLElement element, ResXmlElement resXmlElement){
int count=element.getAttributeCount();
@ -181,4 +196,21 @@ public class XMLFileEncoder {
idBuilder.add(entryBlock.getResourceId(), entryBlock.getName());
}
}
private ResXmlStartNamespace forceCreateNamespace(ResXmlElement resXmlElement,
int resourceId, String prefix){
if(!materials.isForceCreateNamespaces()){
return null;
}
int pkgId = (resourceId>>24) & 0xff;
String uri;
if(pkgId==materials.getCurrentPackageId()){
uri=EncodeUtil.URI_APP;
}else {
uri=EncodeUtil.URI_ANDROID;
}
ResXmlElement root=resXmlElement.getRootResXmlElement();
ResXmlStartNamespace ns=root.getOrCreateNamespace(uri, prefix);
materials.logMessage("Force created ns: "+prefix+":"+uri);
return ns;
}
}

View File

@ -52,10 +52,8 @@ class XMLValuesEncoder {
encodeValue(entryBlock, element);
if(!entryBlock.isNull()){
SpecString specString = getMaterials().getSpecString(name);
entryBlock.setSpecReference(specString);
}
SpecString specString = getMaterials().getSpecString(name);
entryBlock.setSpecReference(specString);
}
void encodeValue(EntryBlock entryBlock, XMLElement element){
String value = getValue(element);

View File

@ -65,7 +65,7 @@ class XMLValuesEncoderStyle extends XMLValuesEncoderBag{
bagItem.setType(ValueType.REFERENCE);
}
bagItem.setData(getMaterials().resolveReference(valueText));
}else if(attributeBag.contains(AttributeValueType.STRING)) {
}else if(attributeBag.isEqualType(AttributeValueType.STRING)) {
bagItem.setValueAsString(valueText);
}else if(EncodeUtil.isEmpty(valueText)) {
bagItem.setTypeAndData(ValueType.NULL, 0);

View File

@ -247,7 +247,7 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
public int getHighestEntryCount(){
int result=0;
for(TypeBlock typeBlock:getChildes()){
int count=typeBlock.getEntryCount();
int count=typeBlock.getEntryBlockArray().childesCount();
if(count>result){
result=count;
}

View File

@ -60,6 +60,12 @@ import java.util.*;
addChild(4, mEndElementContainer);
addChild(5, mEndNamespaceList);
}
public void calculatePositions(){
ResXmlStartElement start = getStartElement();
if(start!=null){
start.calculatePositions();
}
}
public ResXmlAttribute newAttribute(){
return getStartElement().newAttribute();
}

View File

@ -123,7 +123,7 @@ import java.util.regex.Pattern;
return new EncodeResult(ValueType.INT_HEX, parseHex(numString));
}
if(isInteger(numString)){
return new EncodeResult(ValueType.INT_DEC, parseHex(numString));
return new EncodeResult(ValueType.INT_DEC, parseInteger(numString));
}
return null;
}

View File

@ -75,7 +75,7 @@ public class EntryGroup extends ItemGroup<EntryBlock> {
}
boolean renameOk=false;
for(EntryBlock block:items){
if(block==null||block.isNull()){
if(block==null){
continue;
}
if(block.getSpecReference()==specReference){
@ -87,15 +87,22 @@ public class EntryGroup extends ItemGroup<EntryBlock> {
return renameOk;
}
public EntryBlock pickOne(){
EntryBlock defEntryBlock=getDefault();
if(defEntryBlock!=null){
return defEntryBlock;
EntryBlock[] items=getItems();
if(items==null){
return null;
}
Iterator<EntryBlock> itr=iterator(true);
while (itr.hasNext()){
return itr.next();
EntryBlock result = null;
for(EntryBlock entryBlock:items){
if(entryBlock==null){
continue;
}
if(result==null || result.isNull()){
result=entryBlock;
}else if(entryBlock.isDefault()){
return entryBlock;
}
}
return null;
return result;
}
public EntryBlock getDefault(){
Iterator<EntryBlock> itr=iterator(true);

View File

@ -278,28 +278,45 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
private void setByteFlagsB(byte b){
mByteFlagsB.set(b);
}
public IntegerItem getSpecReferenceBlock(){
private IntegerItem getSpecReferenceBlock(){
return mSpecReference;
}
public int getSpecReference(){
if(mSpecReference==null){
return -1;
}
return mSpecReference.get();
}
public void setSpecReference(int ref){
if(mSpecReference==null){
return;
}
boolean created = createNullSpecReference();
int old=mSpecReference.get();
if(ref==old){
return;
}
mSpecReference.set(ref);
updateSpecRef(old, ref);
if(created){
updatePackage();
}
}
public void setSpecReference(SpecString specString){
removeSpecRef();
if(mSpecReference!=null){
mSpecReference.set(specString.getIndex());
if(specString==null){
return;
}
boolean created = createNullSpecReference();
mSpecReference.set(specString.getIndex());
if(created){
updatePackage();
}
}
private boolean createNullSpecReference(){
if(mSpecReference==null){
mSpecReference = new IntegerItem();
mSpecReference.setNull(true);
return true;
}
return false;
}
public BaseResValue getResValue(){
return mResValue;
@ -352,6 +369,13 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
return specString.get();
}
public String getNameOrHex(){
String name = getName();
if(name==null){
name = String.format("@0x%08x", getResourceId());
}
return name;
}
private void setName(String name){
PackageBlock packageBlock=getPackageBlock();
EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId());
@ -378,6 +402,9 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return packageBlock.getName();
}
public SpecString getSpecString(){
if(mSpecReference==null){
return null;
}
PackageBlock packageBlock=getPackageBlock();
if(packageBlock==null){
return null;
@ -486,7 +513,11 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
this.mHeaderSize =new ShortItem();
this.mFlagEntryType =new ByteItem();
this.mByteFlagsB=new ByteItem();
this.mSpecReference = new IntegerItem();
if(mSpecReference==null){
this.mSpecReference = new IntegerItem();
}else if(mSpecReference.isNull()){
mSpecReference.setNull(false);
}
mHeaderSize.setIndex(0);
mFlagEntryType.setIndex(1);
@ -625,11 +656,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
updateSpecRef(-1, getSpecReference());
}
private void updateSpecRef(int oldRef, int newNef){
TypeBlock typeBlock=getTypeBlock();
if(typeBlock==null){
return;
}
PackageBlock packageBlock=typeBlock.getPackageBlock();
PackageBlock packageBlock=getPackageBlock();
if(packageBlock==null){
return;
}
@ -647,11 +674,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
if(mSpecReference==null){
return;
}
TypeBlock typeBlock=getTypeBlock();
if(typeBlock==null){
return;
}
PackageBlock packageBlock=typeBlock.getPackageBlock();
PackageBlock packageBlock=getPackageBlock();
if(packageBlock==null){
return;
}
@ -662,11 +685,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
}
private void updatePackage(){
TypeBlock typeBlock=getTypeBlock();
if(typeBlock==null){
return;
}
PackageBlock packageBlock=typeBlock.getPackageBlock();
PackageBlock packageBlock=getPackageBlock();
if(packageBlock==null){
return;
}
@ -761,37 +780,6 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
return new ResValueInt();
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append(": ");
ResConfig resConfig=getResConfig();
if(resConfig!=null){
builder.append(resConfig.toString());
builder.append(", ");
}
builder.append(" resId=");
builder.append(String.format("0x%08x", getResourceId()));
if(isNull()){
builder.append(", null entry");
return builder.toString();
}
String name=getResourceName();
if(name!=null){
builder.append('(');
builder.append(name);
builder.append(')');
}
BaseResValue baseResValue=getResValue();
if(baseResValue instanceof ResValueInt){
ResValueInt resValueInt=(ResValueInt)baseResValue;
builder.append(" '");
builder.append(resValueInt.toString());
builder.append(" '");
}
return builder.toString();
}
public static String buildResourceName(char prefix, String packageName, String type, String name){
if(name==null){
return null;
@ -811,6 +799,39 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
builder.append(name);
return builder.toString();
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append(": ");
ResConfig resConfig=getResConfig();
if(resConfig!=null){
builder.append(resConfig.toString());
builder.append(", ");
}
String name=getResourceName();
if(name==null){
name=getNameOrHex();
}else{
builder.append(" id=");
builder.append(String.format("0x%08x", getResourceId()));
}
builder.append('(');
builder.append(name);
builder.append(')');
if(isNull()){
builder.append(", null entry");
return builder.toString();
}
BaseResValue baseResValue=getResValue();
if(baseResValue instanceof ResValueInt){
ResValueInt resValueInt=(ResValueInt)baseResValue;
builder.append(" '");
builder.append(resValueInt.toString());
builder.append(" '");
}
return builder.toString();
}
private final static short HEADER_SIZE_BAG = 0x0010;
private final static short HEADER_SIZE_INT = 0x0008;

View File

@ -31,6 +31,9 @@ public class AttributeBag {
public boolean contains(AttributeValueType valueType){
return getFormat().contains(valueType);
}
public boolean isEqualType(AttributeValueType valueType){
return getFormat().isEqualType(valueType);
}
public ValueDecoder.EncodeResult encodeEnumOrFlagValue(String valueString){
if(valueString==null || !isEnumOrFlag()){
return null;

View File

@ -92,6 +92,14 @@ public class AttributeBagItem {
int dataLow = 0xffff & getBagItem().getDataLow();
return (dataLow & value) == value;
}
public boolean isEqualType(AttributeValueType valueType){
if(valueType == null || getItemType()!=AttributeItemType.FORMAT){
return false;
}
int value = 0xff & valueType.getByte();
int dataLow = 0xffff & getBagItem().getDataLow();
return (dataLow == value);
}
public AttributeValueType[] getValueTypes(){
AttributeItemType type=getItemType();
if(type!=AttributeItemType.FORMAT){

View File

@ -33,12 +33,6 @@ public class XMLTextAttribute extends XMLAttribute {
if(unEscape){
return XMLUtil.unEscapeXmlChars(mText);
}
if(mText!=null){
String junk= XMLUtil.unEscapeXmlChars(mText);
if(!mText.equals(junk)){
junk.trim();
}
}
return mText;
}
@Override

View File

@ -530,30 +530,17 @@ public class MXParser implements XmlPullParser
}
return i;
}
public String getPositionDescription ()
@Override
public String getPositionDescription()
{
String fragment = null;
if(posStart <= pos) {
final int start = findFragment(0, buf, posStart, pos);
if(start < pos) {
fragment = new String(buf, start, pos - start);
}
if(bufAbsoluteStart > 0 || start > 0) {
fragment = "..." + fragment;
}
}
return " "+TYPES[ eventType ] +
(fragment != null ? " seen "+printable(fragment)+"..." : "")
+" "+(location != null ? location : "")
+"@"+getLineNumber()+":"+getColumnNumber();
return "line="+getLineNumber()+", col="+getColumnNumber();
}
@Override
public int getLineNumber()
{
return lineNumber;
}
@Override
public int getColumnNumber()
{
return columnNumber;
@ -740,11 +727,13 @@ public class MXParser implements XmlPullParser
return attributeValue[ index ];
}
public String getAttributeValue(String namespace,
String name)
@Override
public String getAttributeValue(String namespace, String name)
{
if(eventType != START_TAG) throw new IndexOutOfBoundsException(
"only START_TAG can have attributes"+getPositionDescription());
if(eventType != START_TAG) {
throw new IndexOutOfBoundsException("only START_TAG can have attributes "
+getPositionDescription());
}
if(name == null) {
throw new IllegalArgumentException("attribute name can not be null");
}
@ -1651,7 +1640,7 @@ public class MXParser implements XmlPullParser
} // skip additional spaces
if(ch != '=') {
throw new XmlPullParserException(
"expected = after attribute name", this, null);
"expected = after attribute name '"+name+processNamespaces+"'", this, null);
}
ch = more();
while(isS(ch)) {
@ -2689,7 +2678,7 @@ public class MXParser implements XmlPullParser
return (ch < LOOKUP_MAX_CHAR && lookupNameChar[ ch ])
|| (ch >= LOOKUP_MAX_CHAR && ch <= '\u2027')
|| (ch >= '\u202A' && ch <= '\u218F')
|| (ch >= '\u2800' && ch <= '\uFFEF');
|| (ch >= '\u2800' && ch <= '\uFFEF') || ch=='@';
}
protected boolean isS(char ch) {

View File

@ -17,10 +17,7 @@ public class XmlPullParserException extends Exception {
super(s);
}
public XmlPullParserException(String msg, XmlPullParser parser, Throwable chain) {
super ((msg == null ? "" : msg+" ")
+ (parser == null ? "" : "(position:"+parser.getPositionDescription()+") ")
+ (chain == null ? "" : "caused by: "+chain));
super(buildMessage(msg, parser));
if (parser != null) {
this.row = parser.getLineNumber();
this.column = parser.getColumnNumber();
@ -30,5 +27,19 @@ public class XmlPullParserException extends Exception {
public Throwable getDetail() { return detail; }
public int getLineNumber() { return row; }
public int getColumnNumber() { return column; }
private static String buildMessage(String msg, XmlPullParser parser){
StringBuilder builder=new StringBuilder();
if(parser!=null){
builder.append("[line=");
builder.append(parser.getLineNumber());
builder.append(", col=");
builder.append(parser.getColumnNumber());
builder.append("] ");
}
if(msg!=null){
builder.append(msg);
}
return builder.toString();
}
}