From caa721b90f4491c8903bfb05dbd80517fd8918d4 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Thu, 5 Jan 2023 12:15:17 -0500 Subject: [PATCH] create namespace validator --- .../lib/apk/ApkModuleXmlDecoder.java | 3 + .../apk/xmldecoder/XMLNamespaceValidator.java | 139 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/main/java/com/reandroid/lib/apk/xmldecoder/XMLNamespaceValidator.java diff --git a/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java b/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java index a04dfec..395962a 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java +++ b/src/main/java/com/reandroid/lib/apk/ApkModuleXmlDecoder.java @@ -17,6 +17,7 @@ package com.reandroid.lib.apk; import com.reandroid.archive.InputSource; import com.reandroid.lib.apk.xmldecoder.XMLBagDecoder; +import com.reandroid.lib.apk.xmldecoder.XMLNamespaceValidator; import com.reandroid.lib.arsc.chunk.PackageBlock; import com.reandroid.lib.arsc.chunk.TableBlock; import com.reandroid.lib.arsc.chunk.TypeBlock; @@ -117,6 +118,8 @@ import java.util.*; File file=new File(resDir, path); logVerbose("Decoding: "+path); + XMLNamespaceValidator namespaceValidator=new XMLNamespaceValidator(resXmlBlock); + namespaceValidator.validate(); XMLDocument xmlDocument=resXmlBlock.decodeToXml(entryStore, packageBlock.getId()); xmlDocument.save(file, true); diff --git a/src/main/java/com/reandroid/lib/apk/xmldecoder/XMLNamespaceValidator.java b/src/main/java/com/reandroid/lib/apk/xmldecoder/XMLNamespaceValidator.java new file mode 100644 index 0000000..6fc4ae0 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/xmldecoder/XMLNamespaceValidator.java @@ -0,0 +1,139 @@ + /* + * 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.lib.apk.xmldecoder; + +import com.reandroid.lib.arsc.chunk.xml.*; + +import java.util.ArrayList; +import java.util.List; + +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 final ResXmlBlock xmlBlock; + private List mAttributeList; + private ResXmlElement mRootElement; + private ResXmlStartNamespace mNsAndroid; + private ResXmlStartNamespace mNsApp; + public XMLNamespaceValidator(ResXmlBlock xmlBlock){ + this.xmlBlock=xmlBlock; + } + public void validate(){ + if(getRootElement()==null){ + return; + } + for(ResXmlAttribute attribute:getAttributes()){ + validate(attribute); + } + } + private void validate(ResXmlAttribute attribute){ + int resourceId=attribute.getNameResourceID(); + if(resourceId==0){ + return; + } + int pkgId=toPackageId(resourceId); + if(isAndroid(pkgId)){ + setAndroidNamespace(attribute); + }else { + setAppNamespace(attribute); + } + } + private void setAppNamespace(ResXmlAttribute attribute){ + if(isValidAppNamespace(attribute)){ + return; + } + ResXmlStartNamespace ns = getOrCreateApp(); + attribute.setNamespaceReference(ns.getUriReference()); + } + private boolean isValidAppNamespace(ResXmlAttribute attribute){ + String uri = attribute.getUri(); + String prefix = attribute.getNamePrefix(); + if(URI_ANDROID.equals(uri) || PREFIX_ANDROID.equals(prefix)){ + return false; + } + if(isEmpty(uri) || isEmpty(prefix)){ + return false; + } + 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 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){ + return pkgId==1; + } + private int toPackageId(int resId){ + return (resId >> 24 & 0xFF); + } + private static boolean isEmpty(String str){ + if(str==null){ + return true; + } + str=str.trim(); + return str.length()==0; + } +}