<?php
/******************************************************
* Android APK File Parser
* Author: Katana
* Version: v0.1
*
* 功能:解析安卓apk包中的压缩XML文件,还原和读取XML内容
* 依赖功能:需要PHP的ZIP包函数支持。
******************************************************/
class ApkParser {
//----------------------
// 公共函数,供外部调用
//----------------------
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 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 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;
//----------------------
// 内部私有函数
//----------------------
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--;
}
}
}
// 没有找到节点
return NULL;
}
return $r;
}
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);
- 1
- 2
前往页