/* * Copyright (C) 2012 Sony Mobile Communications AB * * This file is part of ApkAnalyser. * * 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 andreflect.xml; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.xmlpull.mxp1.MXParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import andreflect.gui.linebuilder.XmlLineFormatter; import brut.androlib.AndrolibException; import brut.androlib.res.data.ResID; import brut.androlib.res.data.ResTable; import brut.androlib.res.decoder.AXmlResourceParser; public class XmlParser { private ZipFile m_apk; private final ArrayList<ZipEntry> m_xmlFiles; public static final String XML_SUFFIX = ".xml"; public static final String MANIFEST = "AndroidManifest.xml"; private XmlManifest manifest = null; private final ResTable mResTable; private final HashMap<Integer, ArrayList<XmlLine>> mInternal = new HashMap<Integer, ArrayList<XmlLine>>(); private final HashMap<Integer, ArrayList<XmlLine>> mExternal = new HashMap<Integer, ArrayList<XmlLine>>(); public static class XmlLine { public ZipEntry entry; public int line; public int id; public XmlLine(ZipEntry entry, int line, int id) { this.id = id; this.entry = entry; this.line = line; } @Override public String toString() { return entry.getName() + " line " + line; } } private XmlResourceChecker resouceChecker = null; public XmlResourceChecker getResourceChecker() { if (resouceChecker == null) { resouceChecker = new XmlResourceChecker(this); } return resouceChecker; } public ResTable getResTable() { return mResTable; } public ZipEntry visitFile(String filename) { for (ZipEntry entry : m_xmlFiles) { if (!entry.isDirectory()) { if (entry.getName().equals(filename)) { return entry; } } } return null; } public XmlParser(File apk, ResTable resTable) { mResTable = resTable; m_xmlFiles = new ArrayList<ZipEntry>(); try { m_apk = new ZipFile(apk); Enumeration<? extends ZipEntry> e = m_apk.entries(); while (e.hasMoreElements()) { ZipEntry entry = e.nextElement(); if (!entry.isDirectory()) { if (entry.getName().endsWith(XML_SUFFIX)) { m_xmlFiles.add(entry); } } } } catch (IOException e) { //ignore because it may not be apk file but a odex file } for (ZipEntry entry : m_xmlFiles) { try { InputStream is = m_apk.getInputStream(entry); IntReader ir = new IntReader(is, false); int c = ir.readInt(); if (c == 0x6D783F3C) { } else { cacheEncodedXmlResourceId(entry); } } catch (IOException e) { e.printStackTrace(); } } } private void cacheEncodedXmlResourceId(ZipEntry entry) { try { AXmlResourceParser parser = new AXmlResourceParser(); XmlResAttrDecoder attDecoder = new XmlResAttrDecoder(mResTable); parser.setAttrDecoder(attDecoder); parser.open(m_apk.getInputStream(entry)); while (true) { int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT) { break; } switch (type) { case XmlPullParser.START_TAG: { for (int i = 0; i != parser.getAttributeCount(); ++i) { int id = parser.getAttributeResourceValue(i, 0); int line = parser.getLineNumber(); if (id != 0) { HashMap<Integer, ArrayList<XmlLine>> cache; boolean internal = false; if (mResTable != null) { try { if (mResTable.getResSpec(id) != null) { internal = true; } } catch (AndrolibException e) { } } if (internal) { cache = mInternal; } else { cache = mExternal; } if (cache.get(id) == null) { ArrayList<XmlLine> xmlines = new ArrayList<XmlLine>(); cache.put(id, xmlines); } cache.get(id).add(new XmlLine(entry, line, id)); //just call att decoder to check if value can be decoded parser.getAttributeValue(i); } } } } } } catch (IOException e) { e.printStackTrace(); } catch (XmlPullParserException e) { e.printStackTrace(); } return; } public ArrayList<ZipEntry> getXmlFiles() { return m_xmlFiles; } public XmlManifest getManifest() { if (manifest == null && m_apk.getEntry(MANIFEST) != null) { try { manifest = parseManifest(m_apk.getInputStream(m_apk.getEntry(MANIFEST))); } catch (IOException e) { e.printStackTrace(); } } return manifest; } private InputStream getInputStream(String name) { InputStream ret = null; for (int i = 0; i < m_xmlFiles.size(); i++) { if (m_xmlFiles.get(i).getName().equals(name)) { try { ret = m_apk.getInputStream(m_xmlFiles.get(i)); } catch (IOException e) { e.printStackTrace(); } } } return ret; } public ArrayList<XmlLine> findXmlInternalRefenence(int resId) { if (mInternal.containsKey(resId)) { return mInternal.get(resId); } else { return new ArrayList<XmlLine>(); } } public ArrayList<XmlLine> findXmlExternalRefenence(int resId) { if (mExternal.containsKey(resId)) { return mExternal.get(resId); } else { return new ArrayList<XmlLine>(); } } public Set<Integer> listXmlExternalReference() { return mExternal.keySet(); } public ArrayList<XmlLine> findXmlAndroidSystemReference() { ArrayList<XmlLine> result = new ArrayList<XmlLine>(); Iterator<Integer> i = mExternal.keySet().iterator(); while (i.hasNext()) { int id = i.next(); if (new ResID(id).package_ == XmlResAttrDecoder.ANDROID_PACKAGE_ID) { ArrayList<XmlLine> xmlLines = mExternal.get(id); for (XmlLine xmlLine : xmlLines) { result.add(xmlLine); } } } return result; } public XmlLineFormatter getXmlLineBuilder(String name, int line, int id, boolean onlyPackage) { XmlLineFormatter xmllb = null; InputStream is = getInputStream(name); IntReader ir = new IntReader(is, false); try { int c = ir.readInt(); if (c == 0x6D783F3C) { xmllb = parseRawXMLLineBuilder(getInputStream(name)); } else { xmllb = parseXMLLineBuilder(getInputStream(name), line, id, onlyPackage); } } catch (IOException e) { e.printStackTrace(); } return xmllb; } private XmlLineFormatter parseRawXMLLineBuilder(InputStream inputStream) { XmlLineFormatter result = new XmlLineFormatter(); try { MXParser parser = new MXParser(); parser.setInput(inputStream, "utf-8"); StringBuilder indent = new StringBuilder(40); final String indentStep = " "; String previousTag = null; boolean needLF = false; while (true) { int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT) { break; } switch (type) { case XmlPullParser.START_DOCUMENT: { result.appendXMLHeader(); break; } case XmlPullParser.START_TAG: { if (previousTag != null) { result.appendLF(); } result.appendText(indent.toString()); result.appendBeginTagBegin(getNamespacePrefix(parser.getPrefix()), parser.getName()); indent.append(indentStep); int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1); int namespaceCount = parser.getNamespaceCount(parser.getDepth()); boolean firstAttrib = false; needLF = (namespaceCount - namespaceCountBefore + parser.getAttributeCount()) > 3; for (int i = namespaceCountBefore; i != namespaceCount; ++i) { if (firstAttrib) { if (needLF) { result.appendLF(); result.appendText(indent.toString()); } else { result.appendSpace(); } } result.appendAttrib("xmlns:"); result.appendAttrib(parser.getNamespacePrefix(i)); result.appendEQ(); result.appendValue(parser.getNamespaceUri(i)); firstAttrib = true; } for (int i = 0; i != parser.getAttributeCount(); ++i) { if (firstAttrib) { if (needLF) { result.appendLF(); result.appendText(indent.toString()); } else { result.appendSpace(); } } result.appendAttrib(getNamespacePrefix(parser.getAttributePrefix(i))); result.appendAttrib(parser.getAttributeName(i)); result.appendEQ(); result.appendValue(parser.getAttributeValue(i)); firstAttrib = true; } result.appendBeginTagEnd(); previousTag = parser.getName(); break; } case XmlPullParser.END_TAG: { indent.setLength(indent.length() - indentStep.length()); if (!parser.getName().equals(previousTag)) { if (previousTag != null) { result.appendLF(); } result.appendText(indent.toString()); } else if (needLF) { result.appendLF(); result.appendText(indent.toString()); needLF = false; } previousTag = null; result.appendEndTag(getNamespacePrefix(parser.getPrefix()), parser.getName()); break; } case XmlPullParser.TEXT: { result.appendText(parser.getText()); break; } } } } catch (IOException e) { e.printStackTrace(); result = null; } catch (XmlPullParserException e) { result = null; e.printStackTrace(); } return result; } private XmlManifest parseManifest(InputStream inputStream) { XmlManifest manifest = new XmlManifest(); boolean hasMainActivity = false; boolean inActivity = false; boolean inIntentFilter = false; String activityName = null; try { AXmlResourceParser parser = new AXmlResourceParser(); XmlResAttrDecoder attDecoder = new XmlResAttrDecoder(mResTable); parser.setAttrDecoder(attDecoder); parser.open(inputStream); while (true) { int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT) { break; } switch (type) { case XmlPullParser.START_TAG: { if (parser.getName().equals("manifest")) { for (int i = 0; i != parser.getAttributeCount(); ++i) { if (parser.getAttributeName(i).equals("package")) { manifest.setPackage(parser.getAttributeValue(i)); } else if (parser.getAttributeName(i).equals("versionName") && !parser.getAttributeValue(i).startsWith("@")) { manifest.setVersion(parser.getAttributeValue(i)); } else if (parser.getAttributeName(i).equals("versionCode") && !parser.getAttributeValue(i).startsWith("@")) { manifest.setVersionCode(parser.getAttributeValue(i)); } } } else if (parser.getName().equals("activity")) { inActivity = true; for (int i = 0; i != parser.getAttributeCount(); ++i) { if (parser.getAttributeName(i).equals("name")) { activityName = parser.getAttributeValue(i); } } } else if (parser.getName().equals("intent-filter") && inActivity) { inIntentFilter = true; } else if (parser.getName().equals("action") && inActivity && inIntentFilter && !hasMainActivity) { for (int i = 0; i != parser.getAttributeCount(); ++i) { if (parser.getAttributeName(i).equals("name") && parser.getAttributeValue(i).equals("android.intent.action.MAIN")) { manifest.setMainActivity(activityName); hasMainActivity = true; } } } else if (parser.getName().equals("category") && inActivity && inIntentFilter && !hasMainActivity) { for (int i = 0; i != parser.getAttributeCount(); ++i) { if (parser.getAttributeName(i).equals("name") && parser.getAttributeValue(i).equals("android.intent.category.LAUNCHER")) { manifest.setMainActivity(activityName); hasMainActivity = true; } } } break; } case XmlPullParser.START_DOCUMENT: break; case XmlPullParser.END_TAG: if (parser.getName().equals("activity")) { inActivity = false; activityName = null; } else if (parser.getName().equals("intent-filter")) { inIntentFilter = false; } case XmlPullParser.TEXT: break; } } } catch (Exception e) { e.printStackTrace(); } return manifest; } private XmlLineFormatter parseXMLLineBuilder(InputStream inputStream, int line, int id, boolean onlyPackage) { XmlLineFormatter result = new XmlLineFormatter(); try { AXmlResourceParser parser = new AXmlResourceParser(); XmlResAttrDecoder attDecoder = new XmlResAttrDecoder(mResTable); parser.setAttrDecoder(attDecoder); parser.open(inputStream); StringBuilder indent = new StringBuilder(40); final String indentStep = " "; String previousTag = null; boolean needLF = false; while (true) { int type = parser.next(); if (type == XmlPullParser.END_DOCUMENT) { break; } switch (type) { case XmlPullParser.START_DOCUMENT: { result.appendXMLHeader(); break; } case XmlPullParser.START_TAG: { if (previousTag != null) { result.appendLF(); } result.appendText(indent.toString()); result.appendBeginTagBegin(getNamespacePrefix(parser.getPrefix()), parser.getName()); indent.append(indentStep); int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1); int namespaceCount = parser.getNamespaceCount(parser.getDepth()); boolean firstAttrib = false; needLF = (namespaceCount - namespaceCountBefore + parser.getAttributeCount()) > 3; for (int i = namespaceCountBefore; i != namespaceCount; ++i) { if (firstAttrib) { if (needLF) { result.appendLF(); result.appendText(indent.toString()); } else { result.appendSpace(); } } result.appendAttrib("xmlns:"); result.appendAttrib(parser.getNamespacePrefix(i)); result.appendEQ(); result.appendValue(parser.getNamespaceUri(i)); firstAttrib = true; } for (int i = 0; i != parser.getAttributeCount(); ++i) { if (firstAttrib) { if (needLF) { result.appendLF(); result.appendText(indent.toString()); } else { result.appendSpace(); } } result.appendAttrib(getNamespacePrefix(parser.getAttributePrefix(i))); result.appendAttrib(parser.getAttributeName(i)); result.appendEQ(); result.appendValue(parser.getAttributeValue(i)); firstAttrib = true; if (parser.getLineNumber() == line) { int resid = parser.getAttributeResourceValue(i, 0); if (resid != 0) { if (onlyPackage) { if (new ResID(resid).package_ == new ResID(id).package_) { result.setCurrentLine(); } } else if (resid == id) { result.setCurrentLine(); } } } } result.appendBeginTagEnd(); previousTag = parser.getName(); break; } case XmlPullParser.END_TAG: { indent.setLength(indent.length() - indentStep.length()); if (!parser.getName().equals(previousTag)) { if (previousTag != null) { result.appendLF(); } result.appendText(indent.toString()); } else if (needLF) { result.appendLF(); result.appendText(indent.toString()); needLF = false; } previousTag = null; result.appendEndTag(getNamespacePrefix(parser.getPrefix()), parser.getName()); break; } case XmlPullParser.TEXT: { result.appendText(parser.getText()); break; } } } } catch (IOException e) { e.printStackTrace(); result = null; } catch (XmlPullParserException e) { result = null; e.printStackTrace(); } return result; } // original xml parser without highlighting // // private String parseXML(InputStream inputStream){ // StringBuilder result=new StringBuilder(); // try{ // AXmlResourceParser parser=new AXmlResourceParser(); // parser.open(inputStream); // StringBuilder indent=new StringBuilder(10); // final String indentStep=" "; // while (true) { // int type=parser.next(); // if (type==XmlPullParser.END_DOCUMENT) { // break; // } // switch (type) { // case XmlPullParser.START_DOCUMENT: // { // result.append(String.format("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")); // break; // } // case XmlPullParser.START_TAG: // { // result.append(String.format("%s<%s%s\n",indent, // getNamespacePrefix(parser.getPrefix()),parser.getName())); // indent.append(indentStep); // // int namespaceCountBefore=parser.getNamespaceCount(parser.getDepth()-1); // int namespaceCount=parser.getNamespaceCount(parser.getDepth()); // for (int i=namespaceCountBefore;i!=namespaceCount;++i) { // result.append(String.format("%sxmlns:%s=\"%s\"\n", // indent, // parser.getNamespacePrefix(i), // parser.getNamespaceUri(i))); // } // // for (int i=0;i!=parser.getAttributeCount();++i) { // result.append(String.format("%s%s%s=\"%s\"\n",indent, // getNamespacePrefix(parser.getAttributePrefix(i)), // parser.getAttributeName(i), // getAttributeValue(parser,i))); // } // result.append(String.format("%s>\n",indent)); // break; // } // case XmlPullParser.END_TAG: // { // indent.setLength(indent.length()-indentStep.length()); // result.append(String.format("%s</%s%s>\n",indent, // getNamespacePrefix(parser.getPrefix()), // parser.getName())); // break; // } // case XmlPullParser.TEXT: // { // result.append(String.format("%s%s\n",indent,parser.getText())); // break; // } // } // } // } // catch (Exception e) { // e.printStackTrace(); // } // return result.toString(); // } private static String getNamespacePrefix(String prefix) { if (prefix == null || prefix.length() == 0) { return ""; } return prefix + ":"; } }