182 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
|     include_once dirname(__FILE__) . "/ApkStream.php";
 | |
| 
 | |
|     class ApkXmlParser 
 | |
|     {    
 | |
|         const END_DOC_TAG    = 0x00100101;
 | |
|         const START_TAG      = 0x00100102;
 | |
|         const END_TAG        = 0x00100103;
 | |
| 
 | |
|         private $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
 | |
|         private $bytes = array();
 | |
|         private $ready = false;
 | |
| 
 | |
|         public static $indent_spaces  = "                                             ";
 | |
| 
 | |
|         /**
 | |
|         * Store the SimpleXmlElement object
 | |
|         * @var SimpleXmlElement
 | |
|         */
 | |
|         private $xmlObject = NULL;
 | |
| 
 | |
| 
 | |
|         public function __construct(ApkStream $apkStream)
 | |
|         {
 | |
|             $this->bytes = $apkStream->getByteArray();
 | |
|         }
 | |
| 
 | |
|         public static function decompressFile($file,$destination = NULL)
 | |
|         {
 | |
|             if(!is_file($file))
 | |
|                 throw new Exception("{$file} is not a regular file");
 | |
| 
 | |
|             $parser = new self(new ApkStream(fopen($file,'rd')));
 | |
|             //TODO : write a method in this class, ->saveToFile();
 | |
|             file_put_contents($destination === NULL ?  $file : $destination,$parser->getXmlString());
 | |
|         }
 | |
| 
 | |
|         public function decompress() 
 | |
|         {
 | |
|             $numbStrings    = $this->littleEndianWord($this->bytes, 4*4);
 | |
|             $sitOff         = 0x24; 
 | |
|             $stOff          = $sitOff + $numbStrings * 4;
 | |
|             $this->bytesTagOff      = $this->littleEndianWord($this->bytes, 3*4);
 | |
| 
 | |
|             for ($ii = $this->bytesTagOff; $ii < count($this->bytes) - 4; $ii += 4):
 | |
|                 if ($this->littleEndianWord($this->bytes, $ii) == self::START_TAG) :
 | |
|                     $this->bytesTagOff = $ii;  
 | |
|                     break;
 | |
|                     endif;
 | |
|                 endfor;
 | |
| 
 | |
| 
 | |
| 
 | |
|             $off            = $this->bytesTagOff;
 | |
|             $indentCount   = 0;
 | |
|             $startTagLineNo = -2;
 | |
| 
 | |
|             while ($off < count($this->bytes)) 
 | |
|             {
 | |
|                 $currentTag     = $this->littleEndianWord($this->bytes, $off);
 | |
|                 $lineNo         = $this->littleEndianWord($this->bytes, $off + 2*4);
 | |
|                 $nameNsSi       = $this->littleEndianWord($this->bytes, $off + 4*4);
 | |
|                 $nameSi         = $this->littleEndianWord($this->bytes, $off + 5*4); 
 | |
| 
 | |
| 
 | |
|                 switch($currentTag)
 | |
|                 {
 | |
|                     case self::START_TAG:
 | |
|                     {
 | |
|                         $tagSix         = $this->littleEndianWord($this->bytes, $off + 6*4);
 | |
|                         $numbAttrs      = $this->littleEndianWord($this->bytes, $off + 7*4); 
 | |
|                         $off           += 9*4;
 | |
|                         $tagName       = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
 | |
|                         $startTagLineNo = $lineNo;
 | |
|                         $attr_string    = ""; 
 | |
| 
 | |
|                         for ($ii=0; $ii < $numbAttrs; $ii++) 
 | |
|                         {
 | |
|                             $attrNameNsSi   = $this->littleEndianWord($this->bytes, $off);  
 | |
|                             $attrNameSi     = $this->littleEndianWord($this->bytes, $off + 1*4);
 | |
|                             $attrValueSi    = $this->littleEndianWord($this->bytes, $off + 2*4);
 | |
|                             $attrFlags      = $this->littleEndianWord($this->bytes, $off + 3*4);  
 | |
|                             $attrResId      = $this->littleEndianWord($this->bytes, $off + 4*4);
 | |
|                             $off += 5*4;
 | |
| 
 | |
|                             $attrName = $this->compXmlString($this->bytes, $sitOff, $stOff, $attrNameSi);
 | |
|                             if($attrValueSi != 0xffffffff)
 | |
|                                 $attrValue =  $this->compXmlString($this->bytes, $sitOff, $stOff, $attrValueSi);
 | |
|                             else
 | |
|                                 $attrValue  = "0x" . dechex($attrResId);
 | |
| 
 | |
|                             $attr_string .= " " . $attrName . "=\"" . $attrValue . "\"";
 | |
| 
 | |
|                         }
 | |
| 
 | |
|                         $this->appendXmlIndent($indentCount, "<". $tagName . $attr_string . ">");
 | |
|                         $indentCount++;
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                     case self::END_TAG:
 | |
|                     {
 | |
|                         $indentCount--;
 | |
|                         $off += 6*4;
 | |
|                         $tagName = $this->compXmlString($this->bytes, $sitOff, $stOff, $nameSi);
 | |
|                         $this->appendXmlIndent($indentCount, "</" . $tagName . ">");
 | |
|                     }
 | |
|                     break;
 | |
| 
 | |
|                     case self::END_DOC_TAG:
 | |
|                     {
 | |
|                         $this->ready = true; 
 | |
|                         break 2;
 | |
|                     }
 | |
|                     break; 
 | |
| 
 | |
|                     default:
 | |
|                         throw new Exception("Unrecognized tag code '"  . dechex($currentTag) . "' at offset " . $off);
 | |
|                         break;
 | |
|                 }
 | |
| 
 | |
| 
 | |
|             }  
 | |
| 
 | |
|         }
 | |
| 
 | |
|         public function compXmlString($xml, $sitOff, $stOff, $str_index) 
 | |
|         {
 | |
|             if ($str_index < 0) 
 | |
|                 return null;
 | |
| 
 | |
|             $strOff = $stOff + $this->littleEndianWord($xml, $sitOff + $str_index * 4);
 | |
|             return $this->compXmlStringAt($xml, $strOff);
 | |
|         }
 | |
| 
 | |
|         public function appendXmlIndent($indent, $str) 
 | |
|         {
 | |
|             $this->appendXml(substr(self::$indent_spaces,0, min($indent * 2, strlen(self::$indent_spaces)))  .  $str);
 | |
|         }
 | |
| 
 | |
|         public function appendXml($str)
 | |
|         {
 | |
|             $this->xml .= $str ."\r\n";
 | |
|         }
 | |
| 
 | |
|         public function compXmlStringAt($arr, $string_offset) 
 | |
|         {
 | |
|             $strlen = $arr[$string_offset + 1] << 8 & 0xff00 | $arr[$string_offset] & 0xff;
 | |
|             $string = "";
 | |
| 
 | |
|             for ($i=0; $i<$strlen; $i++) 
 | |
|                 $string .= chr($arr[$string_offset + 2 + $i * 2]);
 | |
| 
 | |
|             return $string;
 | |
|         } 
 | |
| 
 | |
|         public function littleEndianWord($arr, $off) 
 | |
|         {
 | |
|             return $arr[$off+3] << 24&0xff000000 | $arr[$off+2] << 16&0xff0000 | $arr[$off+1]<<8&0xff00 | $arr[$off]&0xFF;
 | |
|         }
 | |
| 
 | |
|         public function output()
 | |
|         {                              
 | |
|             echo $this->getXmlString();
 | |
|         } 
 | |
| 
 | |
|         public function getXmlString()
 | |
|         {
 | |
|             if(!$this->ready)
 | |
|                 $this->decompress();
 | |
|             return $this->xml;
 | |
|         }
 | |
| 
 | |
|         public function getXmlObject($className = 'SimpleXmlElement')
 | |
|         {  
 | |
|             if($this->xmlObject === NULL || !$this->xmlObject instanceof $className)
 | |
|                 $this->xmlObject = simplexml_load_string($this->getXmlString(),$className);
 | |
| 
 | |
|             return $this->xmlObject;                       
 | |
|         }
 | |
|     }
 |