From b07b8bddfb68480cfd8a12af25801f8faedee1ad Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sun, 7 May 2023 16:58:15 +0200 Subject: [PATCH] better xml namespace handling --- .../reandroid/apk/ApkModuleXmlDecoder.java | 3 +- .../xmldecoder/ResXmlDocumentSerializer.java | 3 +- .../apk/xmldecoder/XMLNamespaceValidator.java | 136 +++++++----------- .../arsc/chunk/xml/ResXmlAttribute.java | 12 ++ .../arsc/chunk/xml/ResXmlElement.java | 114 ++++++++------- .../reandroid/arsc/chunk/xml/ResXmlNode.java | 36 ++--- .../arsc/chunk/xml/ResXmlTextNode.java | 38 +++-- 7 files changed, 166 insertions(+), 176 deletions(-) diff --git a/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java b/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java index 0ca8bc7..2608a13 100644 --- a/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java +++ b/src/main/java/com/reandroid/apk/ApkModuleXmlDecoder.java @@ -211,8 +211,7 @@ public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate } private void serializeXml(int currentPackageId, ResXmlDocument document, File outFile) throws IOException { - XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document); - namespaceValidator.validate(); + XMLNamespaceValidator.validateNamespaces(document); ResXmlDocumentSerializer serializer = getDocumentSerializer(); if(currentPackageId != 0){ serializer.getDecoder().setCurrentPackageId(currentPackageId); diff --git a/src/main/java/com/reandroid/apk/xmldecoder/ResXmlDocumentSerializer.java b/src/main/java/com/reandroid/apk/xmldecoder/ResXmlDocumentSerializer.java index dbc848f..eb5db63 100644 --- a/src/main/java/com/reandroid/apk/xmldecoder/ResXmlDocumentSerializer.java +++ b/src/main/java/com/reandroid/apk/xmldecoder/ResXmlDocumentSerializer.java @@ -121,8 +121,7 @@ public class ResXmlDocumentSerializer implements ResXmlPullParser.DocumentLoaded if(!validateXmlNamespace){ return resXmlDocument; } - XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(resXmlDocument); - namespaceValidator.validate(); + XMLNamespaceValidator.validateNamespaces(resXmlDocument); return resXmlDocument; } private IOException getError(Exception exception){ diff --git a/src/main/java/com/reandroid/apk/xmldecoder/XMLNamespaceValidator.java b/src/main/java/com/reandroid/apk/xmldecoder/XMLNamespaceValidator.java index 53eee30..1f42220 100644 --- a/src/main/java/com/reandroid/apk/xmldecoder/XMLNamespaceValidator.java +++ b/src/main/java/com/reandroid/apk/xmldecoder/XMLNamespaceValidator.java @@ -1,4 +1,4 @@ - /* +/* * Copyright (C) 2022 github.com/REAndroid * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,54 +17,65 @@ package com.reandroid.apk.xmldecoder; import com.reandroid.arsc.chunk.xml.*; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; public class XMLNamespaceValidator { - private static final String URI_ANDROID="http://schemas.android.com/apk/res/android"; - private static final String URI_APP="http://schemas.android.com/apk/res-auto"; - private static final String PREFIX_ANDROID ="android"; - private static final String PREFIX_APP="app"; + private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android"; + private static final String URI_APP = "http://schemas.android.com/apk/res-auto"; + private static final String PREFIX_ANDROID = "android"; + private static final String PREFIX_APP = "app"; private final ResXmlDocument xmlBlock; - private List mAttributeList; - private ResXmlElement mRootElement; - private ResXmlStartNamespace mNsAndroid; - private ResXmlStartNamespace mNsApp; public XMLNamespaceValidator(ResXmlDocument xmlBlock){ this.xmlBlock=xmlBlock; } public void validate(){ - if(getRootElement()==null){ - return; - } - for(ResXmlAttribute attribute:getAttributes()){ - validate(attribute); - } + validateNamespaces(xmlBlock); } - private void validate(ResXmlAttribute attribute){ - int resourceId=attribute.getNameResourceID(); - if(resourceId==0){ - removeNamespace(attribute); - return; + + public static boolean isValid(ResXmlAttribute attribute){ + int resourceId = attribute.getNameResourceID(); + if(resourceId == 0){ + return attribute.getUri() == null; } - int pkgId=toPackageId(resourceId); - if(isAndroid(pkgId)){ - setAndroidNamespace(attribute); + if(isAndroid(toPackageId(resourceId))){ + return isValidAndroidNamespace(attribute); }else { - setAppNamespace(attribute); + return isValidAppNamespace(attribute); } } - private void removeNamespace(ResXmlAttribute attribute){ - attribute.setNamespaceReference(-1); + public static void validateNamespaces(ResXmlDocument resXmlDocument){ + validateNamespaces(resXmlDocument.getResXmlElement()); } - private void setAppNamespace(ResXmlAttribute attribute){ - if(isValidAppNamespace(attribute)){ + public static void validateNamespaces(ResXmlElement element){ + validateNamespaces(element.listAttributes()); + for(ResXmlElement child : element.listElements()){ + validateNamespaces(child); + } + } + + private static void validateNamespaces(Collection attributeList){ + for(ResXmlAttribute attribute : attributeList){ + validateNamespace(attribute); + } + } + private static void validateNamespace(ResXmlAttribute attribute){ + int resourceId = attribute.getNameResourceID(); + if(resourceId == 0){ + attribute.setNamespaceReference(-1); return; } - ResXmlStartNamespace ns = getOrCreateApp(); - attribute.setNamespaceReference(ns.getUriReference()); + if(isAndroid(toPackageId(resourceId))){ + if(!isValidAndroidNamespace(attribute)){ + attribute.setNamespace(URI_ANDROID, PREFIX_ANDROID); + } + }else { + if(!isValidAppNamespace(attribute)){ + attribute.setNamespace(URI_APP, PREFIX_APP); + } + } } - private boolean isValidAppNamespace(ResXmlAttribute attribute){ + + private static boolean isValidAppNamespace(ResXmlAttribute attribute){ String uri = attribute.getUri(); String prefix = attribute.getNamePrefix(); if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){ @@ -75,62 +86,15 @@ public class XMLNamespaceValidator { } return true; } - private void setAndroidNamespace(ResXmlAttribute attribute){ - if(URI_ANDROID.equals(attribute.getUri()) - && PREFIX_ANDROID.equals(attribute.getNamePrefix())){ - return; - } - ResXmlStartNamespace ns = getOrCreateAndroid(); - attribute.setNamespaceReference(ns.getUriReference()); + private static boolean isValidAndroidNamespace(ResXmlAttribute attribute){ + return URI_ANDROID.equals(attribute.getUri()) + && PREFIX_ANDROID.equals(attribute.getNamePrefix()); } - private ResXmlStartNamespace getOrCreateApp(){ - if(mNsApp !=null){ - return mNsApp; - } - ResXmlElement root=getRootElement(); - ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_APP, PREFIX_APP); - String prefix=ns.getPrefix(); - if(PREFIX_ANDROID.equals(prefix) || isEmpty(prefix)){ - ns.setPrefix(PREFIX_APP); - } - mNsApp = ns; - return mNsApp; - } - private ResXmlStartNamespace getOrCreateAndroid(){ - if(mNsAndroid !=null){ - return mNsAndroid; - } - ResXmlElement root=getRootElement(); - ResXmlStartNamespace ns = root.getOrCreateNamespace(URI_ANDROID, PREFIX_ANDROID); - if(!PREFIX_ANDROID.equals(ns.getPrefix())){ - ns.setPrefix(PREFIX_ANDROID); - } - mNsAndroid =ns; - return mNsAndroid; - } - private ResXmlElement getRootElement() { - if(mRootElement==null){ - mRootElement = xmlBlock.getResXmlElement(); - } - return mRootElement; - } - private List getAttributes(){ - if(mAttributeList==null){ - mAttributeList=listAttributes(xmlBlock.getResXmlElement()); - } - return mAttributeList; - } - private List listAttributes(ResXmlElement element){ - List results = new ArrayList<>(element.listAttributes()); - for(ResXmlElement child:element.listElements()){ - results.addAll(listAttributes(child)); - } - return results; - } - private boolean isAndroid(int pkgId){ + + private static boolean isAndroid(int pkgId){ return pkgId==1; } - private int toPackageId(int resId){ + private static int toPackageId(int resId){ return (resId >> 24 & 0xFF); } private static boolean isEmpty(String str){ diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlAttribute.java b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlAttribute.java index a5ea564..735666b 100755 --- a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlAttribute.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlAttribute.java @@ -184,6 +184,18 @@ public class ResXmlAttribute extends ValueItem implements AttributeValue, Compar int getNamespaceReference(){ return getInteger(getBytesInternal(), OFFSET_NS); } + public void setNamespace(String uri, String prefix){ + if(uri == null || prefix == null){ + setNamespaceReference(-1); + return; + } + ResXmlElement parentElement = getParentResXmlElement(); + if(parentElement == null){ + return; + } + ResXmlStartNamespace ns = parentElement.getOrCreateNamespace(uri, prefix); + setNamespaceReference(ns.getUriReference()); + } public void setNamespaceReference(int ref){ if(ref == getNamespaceReference()){ return; diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlElement.java b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlElement.java index 06accd8..0b71a76 100755 --- a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlElement.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlElement.java @@ -110,7 +110,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert } return null; } - public String getEndComment(){ + String getEndComment(){ ResXmlEndElement end = getEndElement(); if(end!=null){ return end.getComment(); @@ -143,36 +143,30 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert public ResXmlAttribute newAttribute(){ return getStartElement().newAttribute(); } + @Override void onRemoved(){ for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){ startNamespace.onRemoved(); } ResXmlStartElement start = getStartElement(); - if(start!=null){ + if(start != null){ start.onRemoved(); } - ResXmlText resXmlText=getResXmlText(); - if(resXmlText!=null){ - resXmlText.onRemoved(); - } - for(ResXmlElement child:listElements()){ - child.onRemoved(); + for(ResXmlNode xmlNode : listXmlNodes()){ + xmlNode.onRemoved(); } } + @Override void linkStringReferences(){ for(ResXmlStartNamespace startNamespace:getStartNamespaceList()){ startNamespace.linkStringReferences(); } ResXmlStartElement start = getStartElement(); - if(start!=null){ + if(start != null){ start.linkStringReferences(); } - ResXmlText resXmlText=getResXmlText(); - if(resXmlText!=null){ - resXmlText.linkStringReferences(); - } - for(ResXmlElement child:listElements()){ - child.linkStringReferences(); + for(ResXmlNode xmlNode : getXmlNodes()){ + xmlNode.linkStringReferences(); } } public ResXmlElement createChildElement(){ @@ -355,13 +349,11 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert @Override public int getDepth(){ - int depth = 0; ResXmlElement parent = getParentResXmlElement(); - while (parent!=null){ - depth++; - parent = parent.getParentResXmlElement(); + if(parent != null){ + return parent.getDepth() + 1; } - return depth; + return 0; } @Override void addEvents(ParserEventList parserEventList){ @@ -423,7 +415,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert if(xmlNode==null){ continue; } - xmlNode.onRemove(); + xmlNode.onRemoved(); mBody.remove(xmlNode); } } @@ -433,6 +425,22 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert public int countResXmlNodes(){ return mBody.size(); } + public boolean hasText(){ + for(ResXmlNode xmlNode : getXmlNodes()){ + if(xmlNode instanceof ResXmlTextNode){ + return true; + } + } + return false; + } + public boolean hasElement(){ + for(ResXmlNode xmlNode : getXmlNodes()){ + if(xmlNode instanceof ResXmlElement){ + return true; + } + } + return false; + } public List listXmlNodes(){ return new ArrayList<>(getXmlNodes()); } @@ -479,8 +487,8 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert return results; } public ResXmlElement getRootResXmlElement(){ - ResXmlElement parent=getParentResXmlElement(); - if(parent!=null){ + ResXmlElement parent = getParentResXmlElement(); + if(parent != null){ return parent.getRootResXmlElement(); } return this; @@ -503,13 +511,31 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert } return null; } + public ResXmlStartNamespace getNamespace(String uri, String prefix){ + if(uri == null || prefix == null){ + return null; + } + for(ResXmlStartNamespace ns : mStartNamespaceList.getChildes()){ + if(uri.equals(ns.getUri()) && prefix.equals(ns.getPrefix())){ + return ns; + } + } + ResXmlElement xmlElement = getParentResXmlElement(); + if(xmlElement != null){ + return xmlElement.getNamespace(uri, prefix); + } + return null; + } public ResXmlStartNamespace getOrCreateNamespace(String uri, String prefix){ - ResXmlStartNamespace exist=getStartNamespaceByUri(uri); - if(exist!=null){ + ResXmlStartNamespace exist = getNamespace(uri, prefix); + if(exist != null){ return exist; } - ResXmlStartNamespace startNamespace=new ResXmlStartNamespace(); - ResXmlEndNamespace endNamespace=new ResXmlEndNamespace(); + return getRootResXmlElement().createNamespace(uri, prefix); + } + public ResXmlStartNamespace createNamespace(String uri, String prefix){ + ResXmlStartNamespace startNamespace = new ResXmlStartNamespace(); + ResXmlEndNamespace endNamespace = new ResXmlEndNamespace(); startNamespace.setEnd(endNamespace); addStartNamespace(startNamespace); @@ -562,7 +588,7 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert public void addStartNamespace(ResXmlStartNamespace item){ mStartNamespaceList.add(item); } - public List getEndNamespaceList(){ + private List getEndNamespaceList(){ return mEndNamespaceList.getChildes(); } public void addEndNamespace(ResXmlEndNamespace item){ @@ -596,26 +622,17 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert public ResXmlStartElement getStartElement(){ return mStartElementContainer.getItem(); } - public void setStartElement(ResXmlStartElement item){ + private void setStartElement(ResXmlStartElement item){ mStartElementContainer.setItem(item); } - public ResXmlEndElement getEndElement(){ + private ResXmlEndElement getEndElement(){ return mEndElementContainer.getItem(); } - public void setEndElement(ResXmlEndElement item){ + private void setEndElement(ResXmlEndElement item){ mEndElementContainer.setItem(item); } - // Use listXmlText() instead to be removed on next version - @Deprecated - public ResXmlText getResXmlText(){ - List xmlTextList=listXmlText(); - if(xmlTextList.size()==0){ - return null; - } - return xmlTextList.get(0); - } public void addResXmlTextNode(ResXmlTextNode xmlTextNode){ mBody.add(xmlTextNode); } @@ -624,16 +641,6 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert addResXmlTextNode(new ResXmlTextNode(xmlText)); } } - // Use addResXmlText() - @Deprecated - public void setResXmlText(ResXmlText xmlText){ - addResXmlText(xmlText); - } - @Deprecated - public void setResXmlText(String text){ - clearChildes(); - addResXmlText(text); - } public void addResXmlText(String text){ if(text==null){ return; @@ -995,13 +1002,14 @@ public class ResXmlElement extends ResXmlNode implements JSONConvert public String toString(){ ResXmlStartElement start = getStartElement(); if(start!=null){ - ResXmlText text=getResXmlText(); StringBuilder builder=new StringBuilder(); builder.append("<"); builder.append(start.toString()); - if(text!=null){ + if(hasText() && !hasElement()){ builder.append(">"); - builder.append(text.toString()); + for(ResXmlText xmlText : listXmlText()){ + builder.append(xmlText.getText()); + } builder.append(""); diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlNode.java b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlNode.java index e402f00..344fbea 100644 --- a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlNode.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlNode.java @@ -1,18 +1,18 @@ - /* - * Copyright (C) 2022 github.com/REAndroid - * - * 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 - * - * http://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. - */ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * 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 + * + * http://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 com.reandroid.arsc.chunk.xml; import com.reandroid.arsc.container.FixedBlockContainer; @@ -23,10 +23,10 @@ public abstract class ResXmlNode extends FixedBlockContainer implements JSONCon ResXmlNode(int childesCount) { super(childesCount); } - void onRemove(){ - } + abstract void onRemoved(); + abstract void linkStringReferences(); public abstract int getDepth(); abstract void addEvents(ParserEventList parserEventList); - public static final String NAME_node_type="node_type"; + public static final String NAME_node_type = "node_type"; } diff --git a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlTextNode.java b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlTextNode.java index 417d138..d50918b 100644 --- a/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlTextNode.java +++ b/src/main/java/com/reandroid/arsc/chunk/xml/ResXmlTextNode.java @@ -1,18 +1,18 @@ - /* - * Copyright (C) 2022 github.com/REAndroid - * - * 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 - * - * http://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. - */ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * 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 + * + * http://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 com.reandroid.arsc.chunk.xml; import com.reandroid.arsc.decoder.ValueDecoder; @@ -75,6 +75,14 @@ public class ResXmlTextNode extends ResXmlNode { getResXmlText().setTextReference(ref); } @Override + void onRemoved(){ + getResXmlText().onRemoved(); + } + @Override + void linkStringReferences(){ + getResXmlText().linkStringReferences(); + } + @Override public String toString(){ String txt=getText(); if(txt!=null){