package com.ghostsq.commander.utils; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserFactory; import android.content.IntentFilter; import android.content.IntentFilter.MalformedMimeTypeException; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.PatternMatcher; import android.util.Log; public final class MnfUtils { private static final String TAG = "MnfUtils"; private ApplicationInfo ai; private Resources rr; private String apk_path; private String mans; public MnfUtils( PackageManager pm, String app_name ) { try { ai = pm.getApplicationInfo( app_name, 0 ); rr = pm.getResourcesForApplication( ai ); } catch( NameNotFoundException e ) { e.printStackTrace(); } } public MnfUtils( String apk_path_ ) { apk_path = apk_path_; } public final String extractManifest() { try { if( mans != null ) return mans; if( ai != null ) apk_path = ai.publicSourceDir; if( apk_path == null ) return null; ZipFile zip = new ZipFile( apk_path ); ZipEntry entry = zip.getEntry( "AndroidManifest.xml" ); if( entry != null ) { InputStream is = zip.getInputStream( entry ); if( is != null ) { ByteArrayOutputStream baos = new ByteArrayOutputStream( (int)entry.getSize() ); byte[] buf = new byte[4096]; int n; while( ( n = is.read( buf ) ) != -1 ) baos.write( buf, 0, n ); is.close(); mans = decompressXML( baos.toByteArray() ); return mans; } } } catch( Throwable e ) { e.printStackTrace(); } return null; } public final Drawable extractIcon() { try { if( apk_path == null ) return null; ZipFile zip = new ZipFile( apk_path ); ZipEntry entry = zip.getEntry( "res/drawable/icon.png" ); if( entry != null ) { InputStream is = zip.getInputStream( entry ); return is != null ? new BitmapDrawable( is ) : null; } Enumeration<? extends ZipEntry> entries = zip.entries(); if( entries != null ) { while( entries.hasMoreElements() ) { entry = entries.nextElement(); if( entry == null ) continue; String efn = entry.getName(); if( efn == null || !efn.startsWith( "res/drawable" ) ) continue; if( efn.contains( "icon" ) ) { InputStream is = zip.getInputStream( entry ); return is != null ? new BitmapDrawable( is ) : null; } } } // TODO: find icon from the manifest } catch( Throwable e ) { Log.e( TAG, "Can't get icon for " + apk_path, e ); } return null; } // http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package // decompressXML -- Parse the 'compressed' binary form of Android XML docs // such as for AndroidManifest.xml in .apk files private final static int endDocTag = 0x00100101; private final static int startTag = 0x00100102; private final static int endTag = 0x00100103; private final String decompressXML( byte[] xml ) { StringBuffer xml_sb = new StringBuffer( 8192 ); // Compressed XML file/bytes starts with 24x bytes of data, // 9 32 bit words in little endian order (LSB first): // 0th word is 03 00 08 00 // 3rd word SEEMS TO BE: Offset at then of StringTable // 4th word is: Number of strings in string table // WARNING: Sometime I indiscriminently display or refer to word in // little endian storage format, or in integer format (ie MSB first). int numbStrings = LEW(xml, 4*4); // StringIndexTable starts at offset 24x, an array of 32 bit LE offsets // of the length/string data in the StringTable. int sitOff = 0x24; // Offset of start of StringIndexTable // StringTable, each string is represented with a 16 bit little endian // character count, followed by that number of 16 bit (LE) (Unicode) chars. int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable // XMLTags, The XML tag tree starts after some unknown content after the // StringTable. There is some unknown data after the StringTable, scan // forward from this point to the flag for the start of an XML start tag. int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word. // Scan forward until we find the bytes: 0x02011000(x00100102 in normal int) for (int ii=xmlTagOff; ii<xml.length-4; ii+=4) { if (LEW(xml, ii) == startTag) { xmlTagOff = ii; break; } } // end of hack, scanning for start of first start tag // XML tags and attributes: // Every XML start and end tag consists of 6 32 bit words: // 0th word: 02011000 for startTag and 03011000 for endTag // 1st word: a flag?, like 38000000 // 2nd word: Line of where this tag appeared in the original source file // 3rd word: FFFFFFFF ?? // 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS // 5th word: StringIndex of Element Name // (Note: 01011000 in 0th word means end of XML document, endDocTag) // Start tags (not end tags) contain 3 more words: // 6th word: 14001400 meaning?? // 7th word: Number of Attributes that follow this tag(follow word 8th) // 8th word: 00000000 meaning?? // Attributes consist of 5 words: // 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF // 1st word: StringIndex of Attribute Name // 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used // 3rd word: Flags? // 4th word: str ind of attr value again, or ResourceId of value // TMP, dump string table to tr for debugging //tr.addSelect("strings", null); //for (int ii=0; ii<numbStrings; ii++) { // // Length of string starts at StringTable plus offset in StrIndTable // String str = compXmlString(xml, sitOff, stOff, ii); // tr.add(String.valueOf(ii), str); //} //tr.parent(); // Step through the XML tree element tags and attributes int off = xmlTagOff; int indent = 0; int startTagLineNo = -2; while( off < xml.length ) { int tag0 = LEW(xml, off); //int tag1 = LEW(xml, off+1*4); int lineNo = LEW(xml, off+2*4); //int tag3 = LEW(xml, off+3*4); int nameNsSi = LEW(xml, off+4*4); int nameSi = LEW(xml, off+5*4); if (tag0 == startTag) { // XML START TAG int tag6 = LEW(xml, off+6*4); // Expected to be 14001400 int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow //int tag8 = LEW(xml, off+8*4); // Expected to be 00000000 off += 9*4; // Skip over 6+3 words of startTag data String name = compXmlString(xml, sitOff, stOff, nameSi); //tr.addSelect(name, null); startTagLineNo = lineNo; // Look for the Attributes StringBuffer sb = new StringBuffer(); for (int ii=0; ii<numbAttrs; ii++) { int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF int attrNameSi = LEW(xml, off+1*4); // AttrName String Index int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF int attrFlags = LEW(xml, off+3*4); int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd off += 5*4; // Skip over the 5 words of an attribute String attrName = compXmlString(xml, sitOff, stOff, attrNameSi); String attrValue= null; if( attrValueSi != -1 ) attrValue = compXmlString(xml, sitOff, stOff, attrValueSi); else { if( rr != null ) try { attrValue = rr.getString( attrResId ); } catch( NotFoundException e ) {} if( attrValue == null ) attrValue = "0x"+Integer.toHexString( attrResId ); } sb.append( "\n" ).append( spaces( indent+1 ) ).append( attrName ).append( "=\"" ).append( attrValue ).append( "\"" ); //tr.add(attrName, attrValue); } xml_sb.append( "\n" ).append( spaces( indent ) ).append( "<" ).append( name ); if( sb.length() > 0 ) xml_sb.append( sb ); xml_sb.append( ">" ); indent++; } else if (tag0 == endTag) { // XML END TAG indent--; off += 6*4; // Skip over 6 words of endTag data String name = compXmlString(xml, sitOff, stOff, nameSi); xml_sb.append( "\n" ).append( spaces( indent ) ).append( "</" ).append( name ).append( ">" ); // prtIndent(indent, "</"+name+"> (line "+startTagLineNo+"-"+lineNo+")"); //tr.parent(); // Step back up the NobTree } else if (tag0 == endDocTag) { // END OF XML DOC TAG break; } else { Log.e( TAG, " Unrecognized tag code '"+Integer.toHexString(tag0) +"' at offset "+off); break; } } // end of while loop scanning tags and attributes of XML tree Log.v( TAG, " end at offset "+off ); return xml_sb.toString(); } // end of decompressXML private final String compXmlString(byte[] xml, int sitOff, int stOff, int strInd) { if (strInd < 0) return null; int strOff = stOff + LEW(xml, sitOff+strInd*4); return compXmlStringAt(xml, strOff); } private final String spaces( int i ) { char[] dummy = new char[i*2]; Arrays.fill( dummy, ' ' ); return new String( dummy ); } // compXmlStringAt -- Return the string stored in StringTable format at // offset strOff. This offset points to the 16 bit string length, which // is followed by that number of 16 bit (Unicode) chars. private final String compXmlStringAt(byte[] arr, int strOff) { int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff; byte[] chars = new byte[strLen]; for (int ii=0; ii<strLen; ii++) { chars[ii] = arr[strOff+2+ii*2]; } return new String(chars); // Hack, just use 8 byte chars } // end of compXmlStringAt // LEW -- Return value of a Little Endian 32 bit word from the byte array // at offset off. private final int LEW(byte[] arr, int off) { return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000 | arr[off+1]<<8&0xff00 | arr[off]&0xFF; } // end of LEW public final IntentFilter[] getIntentFilters( String act_name ) { try { if( mans == null ) mans = extractManifest(); if( mans != null && mans.length() > 0 ) { ArrayList<IntentFilter> list = new ArrayList<IntentFilter>(); XmlPullParserFactory factory; factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser(); xpp.setInput( new StringReader( mans ) ); int et; while( ( et = xpp.next() ) != XmlPullParser.END_DOCUMENT ) { if( et == XmlPullParser.START_TAG && "activity".equals( xpp.getName() ) ) { String can = xpp.getAttributeValue( null, "name" ); if( act_name.indexOf( can ) >= 0 ) { // ??? why not exact match? int d = xpp.getDepth(); while( ( et = xpp.next() ) != XmlPullParser.END_DOCUMENT && ( d < xpp.getDepth() || et != XmlPullParser.END_TAG ) ) { if( "intent-filter".equals( xpp.getName() ) ) { IntentFilter inf = new IntentFilter(); initIntentFilterFromXml( inf, xpp ); list.add( inf ); } } break; } } } if( list.size() > 0 ) { IntentFilter[] ret = new IntentFilter[list.size()]; return list.toArray( ret ); } } } catch( Exception e ) { e.printStackTrace(); } return null; } private static final boolean initIntentFilterFromXml( IntentFilter inf, XmlPullParser xpp ) { try { int outerDepth = xpp.getDepth(); int type; final String NAME = "name"; while( (type = xpp.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || xpp.getDepth() > outerDepth) ) { if( type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT ) continue; String tag = xpp.getName(); if( tag.equals( "action" ) ) { String name = xpp.getAttributeValue( null, NAME ); if( name != null ) inf.addAction( name ); } else if( tag.equals( "category" ) ) { String name = xpp.getAttributeValue( null, NAME ); if( name != null ) inf.addCategory( name ); } else if( tag.equals( "data" ) ) { int na = xpp.getAttributeCount(); for( int i = 0; i < na; i++ ) { String port = null; String an = xpp.getAttributeName( i ); String av = xpp.getAttributeValue( i ); if( "mimeType".equals( an ) ) { try { inf.addDataType( av ); } catch( MalformedMimeTypeException e ) { } } else if( "scheme".equals( an ) ) { inf.addDataScheme( av ); } else if( "host".equals( an ) ) { inf.addDataAuthority( av, port ); } else if( "port".equals( an ) ) { port = av; } else if( "path".equals( an ) ) { inf.addDataPath( av, PatternMatcher.PATTERN_LITERAL ); } else if( "pathPrefix".equals( an ) ) { inf.addDataPath( av, PatternMatcher.PATTERN_PREFIX ); } else if( "pathPattern".equals( an ) ) { inf.addDataPath( av, PatternMatcher.PATTERN_SIMPLE_GLOB ); } } } } return true; } catch( Exception e ) { e.printStackTrace(); } return false; } /* public static boolean compareIntentFilters( IntentFilter if1, IntentFilter if2 ) { try { int ca1 = if1.countActions(); int ca2 = if2.countActions(); if( ca1 != ca2 ) return false; for( int i = 0; i< ca1; i++ ) if( !if1.getAction( i ).equals( if2.getAction( i ) ) ) return false; int cc1 = if1.countCategories(); int cc2 = if2.countCategories(); if( cc1 != cc2 ) return false; for( int i = 0; i< cc1; i++ ) if( !if1.getCategory( i ).equals( if2.getCategory( i ) ) ) return false; int cd1 = if1.countDataTypes(); int cd2 = if2.countDataTypes(); if( cd1 != cd2 ) return false; for( int i = 0; i< cd1; i++ ) if( !if1.getDataType( i ).equals( if2.getDataType( i ) ) ) return false; int cs1 = if1.countDataSchemes(); int cs2 = if2.countDataSchemes(); if( cs1 != cs2 ) return false; for( int i = 0; i< cs1; i++ ) if( !if1.getDataScheme( i ).equals( if2.getDataScheme( i ) ) ) return false; return true; } catch( Exception e ) { } return false; } */ }