class ApkParser{
//----------------------
// 公共函數(shù),供外部調(diào)用
//----------------------
public function
open($apk_file, $xml_file='AndroidManifest.xml'){
$zip = new ZipArchive;
if ($zip->open($apk_file) ===
TRUE) {
$xml =
$zip->getFromName($xml_file);
$zip->close();
if
($xml){
try {
return
$this->parseString($xml);
}catch (Exception $e){
}
}
}
return false;
}
public function
parseString($xml){
$this->xml = $xml;
$this->length =
strlen($xml);
$this->root =
$this->parseBlock(self::AXML_FILE);
return true;
}
public function
getXML($node=NULL, $lv=-1){
if ($lv == -1) $node =
$this->root;
if (!$node) return '';
if ($node['type'] == self::END_TAG) $lv--;
$xml = ($node['line'] == 0 || $node['line'] ==
$this->line) ? '' : "\n".str_repeat('
', $lv);
$xml .= $node['tag'];
$this->line =
$node['line'];
foreach ($node['child'] as $c){
$xml .=
$this->getXML($c, $lv+1);
}
return $xml;
}
public function
getPackage(){
return
$this->getAttribute('manifest', 'package');
}
public function
getVersionName(){
return
$this->getAttribute('manifest',
'android:versionName');
}
public function
getVersionCode(){
return
$this->getAttribute('manifest',
'android:versionCode');
}
public function
getAppName(){
return
$this->getAttribute('manifest/application',
'android:name');
}
public function
getDefaultCompany(){
for ($id=0; true; $id++){
$act =
$this->getAttribute("manifest/application/meta-data[{$id}]",
'android:name');
if (!$act)
break;
if ($act
== 'DEFAULT_COMPANY') return
$this->getMetadata($id);
}
return NULL;
}
public function
getMainActivity(){
for ($id=0; true; $id++){
$act =
$this->getAttribute("manifest/application/activity[{$id}]/intent-filter/action",
'android:name');
if (!$act)
break;
if ($act
== 'android.intent.action.MAIN') return
$this->getActivity($id);
}
return NULL;
}
public function
getActivity($idx=0){
$idx = intval($idx);
return
$this->getAttribute("manifest/application/activity[{$idx}]",
'android:name');
}
public function
getMetadata($idx=0){
$idx = intval($idx);
return
$this->getAttribute("manifest/application/meta-data[{$idx}]",
'android:value');
}
public function
getAttribute($path, $name){
$r =
$this->getElement($path);
if (is_null($r)) return NULL;
if (isset($r['attrs'])){
foreach
($r['attrs'] as $a){
if ($a['ns_name'] == $name)
return $this->getAttributeValue($a);
}
}
return NULL;
}
//----------------------
// 類型常量定義
//----------------------
const AXML_FILE
=
0x00080003;
const STRING_BLOCK
= 0x001C0001;
const RESOURCEIDS
= 0x00080180;
const START_NAMESPACE
=
0x00100100;
const END_NAMESPACE
= 0x00100101;
const START_TAG
=
0x00100102;
const END_TAG
= 0x00100103;
const TEXT
=
0x00100104;
const TYPE_NULL
=0;
const TYPE_REFERENCE
=1;
const TYPE_ATTRIBUTE
=2;
const TYPE_STRING
=3;
const TYPE_FLOAT
=4;
const TYPE_DIMENSION
=5;
const TYPE_FRACTION
=6;
const TYPE_INT_DEC
=16;
const TYPE_INT_HEX
=17;
const TYPE_INT_BOOLEAN
=18;
const
TYPE_INT_COLOR_ARGB8 =28;
const
TYPE_INT_COLOR_RGB8 =29;
const
TYPE_INT_COLOR_ARGB4 =30;
const
TYPE_INT_COLOR_RGB4 =31;
const UNIT_MASK
=
15;
private static
$RADIX_MULTS = array(0.00390625, 3.051758E-005, 1.192093E-007,
4.656613E-010);
private static
$DIMENSION_UNITS =
array("px","dip","sp","pt","in","mm","","");
private static
$FRACTION_UNITS =
array("%","%p","","","","","","");
private $xml='';
private $length =
0;
private $stringCount =
0;
private $styleCount
= 0;
private $stringTab =
array();
private $styleTab
= array();
private $resourceIDs =
array();
private $ns =
array();
private $cur_ns =
NULL;
private $root =
NULL;
private $line = 0;
//----------------------
// 內(nèi)部私有函數(shù)
//----------------------
private function
getElement($path){
if (!$this->root) return
NULL;
$ps = explode('/', $path);
$r =
$this->root;
foreach ($ps as $v){
if
(preg_match('/([^\[]+)\[([0-9]+)\]$/', $v, $ms)){
$v = $ms[1];
$off = $ms[2];
}else
{
$off = 0;
}
foreach
($r['child'] as $c){
if ($c['type'] ==
self::START_TAG && $c['ns_name'] ==
$v){
if ($off == 0){
$r = $c;
continue 2;
}else {
$off--;
}
}
}
//
沒有找到節(jié)點
return
NULL;
}
return $r;
var_dump($r);exit;
}
private function
parseBlock($need = 0){
$o = 0;
$type = $this->get32($o);
if ($need &&
$type != $need) throw new Exception('Block Type Error', 1);
$size = $this->get32($o);
if ($size < 8 || $size
> $this->length) throw new
Exception('Block Size Error', 2);
$left = $this->length -
$size;
$props = false;
switch ($type){
case
self::AXML_FILE:
$props = array(
'line' => 0,
'tag' => '<?xml
version="1.0" encoding="utf-8"?>'
);
break;
case
self::STRING_BLOCK:
$this->stringCount =
$this->get32($o);
$this->styleCount =
$this->get32($o);
$o += 4;
$strOffset =
$this->get32($o);
$styOffset =
$this->get32($o);
$strListOffset =
$this->get32array($o,
$this->stringCount);
$styListOffset =
$this->get32array($o,
$this->styleCount);
$this->stringTab = $this->stringCount
> 0 ? $this->getStringTab($strOffset,
$strListOffset) : array();
$this->styleTab =
$this->styleCount > 0 ?
$this->getStringTab($styOffset, $styListOffset) :
array();
$o = $size;
break;
case
self::RESOURCEIDS:
$count = $size / 4 - 2;
$this->resourceIDs =
$this->get32array($o, $count);
break;
case
self::START_NAMESPACE:
$o += 8;
$prefix =
$this->get32($o);
$uri =
$this->get32($o);
if
(empty($this->cur_ns)){
$this->cur_ns = array();
$this->ns[] =
&$this->cur_ns;
}
$this->cur_ns[$uri] = $prefix;
break;
case
self::END_NAMESPACE:
$o += 8;
$prefix =
$this->get32($o);
$uri =
$this->get32($o);
if
(empty($this->cur_ns)) break;
unset($this->cur_ns[$uri]);
break;
case
self::START_TAG:
$line =
$this->get32($o);
$o += 4;
$attrs = array();
$props = array(
'line' => $line,
'ns' =>
$this->getNameSpace($this->get32($o)),
'name' =>
$this->getString($this->get32($o)),
'flag' =>
$this->get32($o),
'count' =>
$this->get16($o),
'id' =>
$this->get16($o)-1,
'class' =>
$this->get16($o)-1,
'style' =>
$this->get16($o)-1,
'attrs' =>
&$attrs
);
$props['ns_name'] =
$props['ns'].$props['name'];
for ($i=0; $i
< $props['count']; $i++){
$a = array(
'ns'
=>
$this->getNameSpace($this->get32($o)),
'name'
=>
$this->getString($this->get32($o)),
'val_str'
=> $this->get32($o),
'val_type'
=> $this->get32($o),
'val_data'
=> $this->get32($o)
);
$a['ns_name'] = $a['ns'].$a['name'];
$a['val_type'] >>=
24;
$attrs[] = $a;
}
// 處理TAG字符串
$tag =
"<{$props['ns_name']}";
foreach
($this->cur_ns as $uri =>
$prefix){
$uri =
$this->getString($uri);
$prefix =
$this->getString($prefix);
$tag .= " xmlns:{$prefix}=\"{$uri}\"";
}
foreach ($props['attrs'] as
$a){
$tag .= " {$a['ns_name']}=\"".
$this->getAttributeValue($a).
'"';
}
$tag .=
'>';
$props['tag'] = $tag;
unset($this->cur_ns);
$this->cur_ns
= array();
$this->ns[] =
&$this->cur_ns;
$left = -1;
break;
case
self::END_TAG:
$line =
$this->get32($o);
$o += 4;
$props = array(
'line' => $line,
'ns' =>
$this->getNameSpace($this->get32($o)),
'name' =>
$this->getString($this->get32($o))
);
$props['ns_name'] =
$props['ns'].$props['name'];
$props['tag'] =
"</{$props['ns_name']}>";
if
(count($this->ns) > 1){
array_pop($this->ns);
unset($this->cur_ns);
$this->cur_ns =
array_pop($this->ns);
$this->ns[] =
&$this->cur_ns;
}
break;
case
self::TEXT:
$o += 8;
$props = array(
'tag' =>
$this->getString($this->get32($o))
);
$o += 8;
break;
default:
throw new Exception('Block
Type Error', 3);
break;
}
$this->skip($o);
$child = array();
while ($this->length
> $left){
$c =
$this->parseBlock();
if ($props
&& $c) $child[] = $c;
if ($left
== -1 && $c['type'] ==
self::END_TAG){
$left =
$this->length;
break;
}
}
if ($this->length != $left) throw
new Exception('Block Overflow Error', 4);
if ($props){
$props['type'] = $type;
$props['size'] = $size;
$props['child'] = $child;
return
$props;
}else {
return
false;
}
}
private function
getAttributeValue($a){
$type = &$a['val_type'];
$data = &$a['val_data'];
switch ($type){
case
self::TYPE_STRING:
return
$this->getString($a['val_str']);
case
self::TYPE_ATTRIBUTE:
return sprintf('?%sX',
self::_getPackage($data), $data);
case
self::TYPE_REFERENCE:
return sprintf('@%sX',
self::_getPackage($data), $data);
case
self::TYPE_INT_HEX:
return sprintf('0xX',
$data);
case
self::TYPE_INT_BOOLEAN:
return ($data != 0 ? 'true' :
'false');
case
self::TYPE_INT_COLOR_ARGB8:
case
self::TYPE_INT_COLOR_RGB8:
case
self::TYPE_INT_COLOR_ARGB4:
case
self::TYPE_INT_COLOR_RGB4:
return sprintf('#X',
$data);
case
self::TYPE_DIMENSION:
return
$this->_complexToFloat($data).self::$DIMENSION_UNITS[$data
& self::UNIT_MASK];
case
self::TYPE_FRACTION:
return
$this->_complexToFloat($data).self::$FRACTION_UNITS[$data
& self::UNIT_MASK];
case
self::TYPE_FLOAT:
return
$this->_int2float($data);
}
if ($type >=self::TYPE_INT_DEC
&& $type <
self::TYPE_INT_COLOR_ARGB8){
return
(string)$data;
}
return sprintf('<0x%X, type
0xX>', $data, $type);
}
private function
_complexToFloat($data){
return (float)($data &
0xFFFFFF00) *
self::$RADIX_MULTS[($data>>4)
& 3];
}
private function
_int2float($v) {
$x = ($v & ((1
<< 23) - 1)) + (1
<< 23) * ($v
>> 31 | 1);
$exp = ($v >> 23
& 0xFF) - 127;
return $x * pow(2, $exp - 23);
}
private static function
_getPackage($data){
return ($data >>
24 == 1) ? 'android:' : '';
}
private function
getStringTab($base, $list){
$tab = array();
foreach ($list as $off){
$off +=
$base;
$len =
$this->get16($off);
$mask =
($len >> 0x8) &
0xFF;
$len =
$len & 0xFF;
if ($len
== $mask){
if ($off + $len
> $this->length) throw new
Exception('String Table Overflow', 11);
$tab[] =
substr($this->xml, $off, $len);
}else
{
if ($off + $len * 2
> $this->length) throw new
Exception('String Table Overflow', 11);
$str =
substr($this->xml, $off, $len * 2);
$tab[] =
mb_convert_encoding($str, 'UTF-8', 'UCS-2LE');
}
}
return $tab;
}
private function
getString($id){
if ($id > -1
&& $id <
$this->stringCount){
return
$this->stringTab[$id];
}else {
return
'';
}
}
private function
getNameSpace($uri){
for ($i=count($this->ns); $i
> 0; ){
$ns =
$this->ns[--$i];
if
(isset($ns[$uri])){
$ns =
$this->getString($ns[$uri]);
if (!empty($ns)) $ns .=
':';
return $ns;
}
}
return '';
}
private function
get32(&$off){
$int = unpack('V',
substr($this->xml, $off, 4));
$off += 4;
return array_shift($int);
}
private function
get32array(&$off, $size){
if ($size <= 0) return
NULL;
$arr = unpack('V*',
substr($this->xml, $off, 4 * $size));
if (count($arr) != $size) throw new
Exception('Array Size Error', 10);
$off += 4 * $size;
return $arr;
}
private function
get16(&$off){
$int = unpack('v',
substr($this->xml, $off, 2));
$off += 2;
return array_shift($int);
}
private function
skip($size){
$this->xml =
substr($this->xml, $size);
$this->length -= $size;
}
}
|