* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 * @category PHK * @package PHK *///========================================================================== namespace PHK { if (!class_exists('PHK\Proxy',false)) { //============================================================================= /** * The 'back-end' object managing physical access to the package file. This * object is created and called by the stream wrapper on cache misses. * * Runtime code -> 100% read-only except setBufferInterp(). * * API status: Private * Included in the PHK PHP runtime: Yes * Implemented in the extension: No *///========================================================================== class Proxy { //========== Class constants =============== /** The size of the interp line (fixed size) */ const INTERP_LEN=64; /** The size of a version string in the magic block - Up to 12 bytes */ const VERSION_SIZE=12; /** The size of each offset field. As offset are limited to 11 bytes, the * theoritical max size of a PHK archive is 100 G bytes. Not tested :) */ const OFFSET_SIZE=11; /** The magic string. This is the string we identify to recognize a PHK archive */ const MAGIC_STRING="#PHK M\024\x8\6\3"; const MAGIC_STRING_LEN=10; /** INTERP_LEN + 6 */ const MAGIC_STRING_OFFSET=70; /** The size of the magic block */ const MAGIC_LINE_LEN=177; /** The name of the optional Automap section, when it exists */ const AUTOMAP_SECTION='AUTOMAP'; /** The offset of the CRC field, from the beginning of the archive file. * The CRC field is 8 bytes long (32 bits in hexadecimal) */ const CRC_OFFSET=200; //========== Instance data =============== /** @var string Package path */ private $path; /** @var PHK\Virtual\Tree Section tree */ protected $stree=null; /** @var PHK\Virtual\Tree File tree */ public $ftree=null; /** @var integer Mount flags */ protected $flags; /** @var PHK\PkgFileSpace File Handler */ protected $fspace; /** @var array Magic values */ private $magic=null; //========== Class methods =============== /** * Constructor * * This method must be called only from PHK\Mgr::proxy() * * @param string mount point * * @throws \Exception */ public function __construct($path,$flags) { try { Tools\Util::slowPath(); //Tools\Util::trace("Starting proxy init");//TRACE $this->path=$path; $this->flags=$flags; if (!($this->flags & \PHK::IS_CREATOR)) { // fileIsPackage() moved here from \PHK\Mgr::computeMnt() because we don't // need to check this if data is already in cache. if (! self::fileIsPackage($path)) throw new \Exception($path.'is not a PHK package'); $this->fspace= new PkgFileSpace($path,$flags); $this->fspace->open(); // Get magic block $this->getMagicValues(); // Check that file size corresponds to the value stored in the magic block. // Done only once in slow path because, if the file size changes, the // modification date will change too, and thus the mount point. if ($this->fspace->size()!=$this->magic['fs']) // Check file size Tools\Util::formatError('Invalid file size. Should be '.$this->magic['fs']); // Import section tree $this->stree=Virtual\Tree::createFromEdata( $this->fspace->readBlock($this->magic['sso'] ,$this->magic['sto']-$this->magic['sso']) ,new PkgFileSpace($this->fspace,$this->magic['sto'] ,$this->magic['fto']-$this->magic['sto'])); $this->ftree=Virtual\Tree::createFromEdata($this->section('FTREE') ,new PkgFileSpace($this->fspace,$this->magic['fto'] ,$this->magic['sio']-$this->magic['fto'])); $this->fspace->close(); // We keep the file open during init phase } else { $this->ftree=Virtual\Tree::createEmpty(); $this->stree=Virtual\Tree::createEmpty(); } } catch (\Exception $e) { throw new \Exception('While initializing PHK proxy - '.$e->getMessage()); } //Tools\Util::trace("Ending init - path=$path");//TRACE } //--------- public function crcCheck() { try { self::checkCrcBuffer($this->fspace->readBlock()); } catch(\Exception $e) { throw new \Exception($this->path.': file is corrupted - '.$e->getMessage()); } } //--------------------------------- /** * Inserts or clears a CRC in a memory buffer * * @static * @param string $buffer The original buffer whose CRC will be overwritten * @param string $crc If set, the CRC as an 8-char string (in hexadecimal). If * not set, we clear the CRC (set it to '00000000'). * @return string The modified buffer */ public static function insertCrc($buffer,$crc) { return substr_replace($buffer,$crc,self::CRC_OFFSET,8); } //-------------------------------- /** * Returns the CRC extracted from a memory buffer (not the computed one) * * @param string $buffer * @return string The extracted 8-char hex CRC */ private static function getCrc($buffer) { return substr($buffer,self::CRC_OFFSET,8); } //--------------------------------- /** * Computes a CRC from a given memory buffer * * As the given buffer already contains a CRC, we first clear it. * * @param string $buffer * @return string The computed 8-char hex CRC */ private static function computeCrc($buffer) { return hash('adler32',self::insertCrc($buffer,'00000000')); } //--------------------------------- /** * Checks a memory buffer's CRC * * The memory buffer is supposed to contain a whole PHK archive. * * No return value: if the CRC check fails, an exception is thrown. * * @param string $buffer * @return void * @throws \Exception */ public static function checkCrcBuffer($buffer) { if (self::computeCrc($buffer) !== self::getCrc($buffer)) throw new \Exception('CRC check failed'); } //--------------------------------- /** * Computes and inserts a CRC in a memory buffer * * @param string $buffer * @return string The modified buffer */ public static function fixCrc($buffer) { return self::insertCrc($buffer,self::computeCrc($buffer)); } //--------- /** * Check if a given path contains a PHK package * * @param string $path path to check (can be virtual) * @return boolean */ public static function fileIsPackage($path) { if (filesize($path)< (self::INTERP_LEN+self::MAGIC_LINE_LEN)) return false; if (($fp=fopen($path,'rb',false))===false) return false; if (fseek($fp,self::MAGIC_STRING_OFFSET) != 0) return false; if (($m=fread($fp,self::MAGIC_STRING_LEN))===false) return false; fclose($fp); return ($m===self::MAGIC_STRING); } //--------- /** * Check if a data buffer contains a PHK package * * @param string $data data buffer to check * @return boolean */ public static function dataIsPackage($data) { if (strlen($data) < (self::INTERP_LEN+self::MAGIC_LINE_LEN)) return false; return (substr($data,self::MAGIC_STRING_OFFSET,self::MAGIC_STRING_LEN) ===self::MAGIC_STRING); } //--------------------------------- /** * Extracts the values out of a magic line buffer * * Note: A package is signed if (Signature offset != File size) * * @param string $buf A magic line content * @return array An array containing the magic values */ public function getMagicValues() { $buf=$this->fspace->readBlock(self::INTERP_LEN,self::MAGIC_LINE_LEN); $fsize=(int)substr($buf,47,self::OFFSET_SIZE); $sio=(int)substr($buf,121,self::OFFSET_SIZE); $crc=null; sscanf(substr($buf,136,8),'%08x',$crc); $this->magic=array( 'mv' => trim(substr($buf,18,self::VERSION_SIZE)), // Minimum required version 'v' => trim(substr($buf,32,self::VERSION_SIZE)), // Version 'fs' => $fsize, // File size 'po' => (int)substr($buf,61,self::OFFSET_SIZE), // Prolog offset 'sso' => (int)substr($buf,76,self::OFFSET_SIZE), // Serialized sections offset 'sto' => (int)substr($buf,91,self::OFFSET_SIZE), // Section table offset 'fto' => (int)substr($buf,106,self::OFFSET_SIZE), // File table offset 'sio' => $sio, // Signature offset 'pco' => (int)substr($buf,148,self::OFFSET_SIZE), // PHP code offset 'pcs' => (int)substr($buf,163,self::OFFSET_SIZE), // PHP code length 'crc' => $crc, 'signed' => ($sio != $fsize)); } //--------------------------------- public function magicField($name) { return $this->magic[$name]; } //--------------------------------- /** * Brings all data in memory * * After this function has run, we never access the package file any more. * * @see \PHK\Virtual\DC implements the data cache * * @return void */ private function cacheData() { $this->stree->walk('read'); $this->ftree->walk('read'); } //--------------------------------- /** * Clears the data cache * * @see \PHK\Virtual\DC implements the data cache * * @return void */ private function clearCache() { $this->stree->walk('clear_cache'); $this->ftree->walk('clear_cache'); } //--------------------------------- public function pathList() { return $this->ftree->pathList(); } //--------------------------------- public function sectionList() { return $this->stree->pathList(); } //--------------------------------- /** * Is this package digitally signed ? * * @return boolean */ public function signed() { return $this->magic['signed']; } //----- /** * Gets interpreter string * * If the interpreter is defined, returns it. Else, returns an empty string * * @return string * @throws \Exception if the interpreter string is invalid */ public function interp() { $block=$this->fspace->readBlock(0,self::INTERP_LEN); if ((($block{0}!='#')||($block{1}!='!')) && (($block{0}!='<')||($block{1}!='?'))) throw new \Exception('Invalid interpreter block'); return ($block{0}=='#') ? trim(substr($block,2)) : ''; } //----- /** * Builds an interpreter block from an interpreter string * * Note: can be applied to a signed package as the signature ignores the * interpreter block and the CRC. * @param string $interp Interpreter to set or empty string to clear * @return string Interpreter block (INTERP_LEN). Including trailing '\n' */ public static function interpBlock($interp) { if (($interp!=='') && (strlen($interp) > (\PHK\Proxy::INTERP_LEN-3))) throw new \Exception('Length of interpreter string is limited to ' .(\PHK\Proxy::INTERP_LEN-3).' bytes'); // Keep ''; else return '#!'.str_pad($interp,\PHK\Proxy::INTERP_LEN-3)."\n"; } //----- /** * Inserts a new interpreter block in a file's content * * Allows a PHK user to change its interpreter string without * having to use the \PHK\Build\Creator kit. * * Note: can be applied to a signed package as the signature ignores the * interpreter block and the CRC. * * @param string $path PHK archive's path * @param string $interp Interpreter string to set (empty to clear) * @return string The modified buffer (the file is not overwritten) */ public static function setBufferInterp($path,$interp='') { return self::fixCrc(substr_replace(Tools\Util::readFile($path) ,self::interpBlock($interp),0,\PHK\Proxy::INTERP_LEN)); } //----- /** * The version of the \PHK\Build\Creator tool this package was created from * * @return string Version */ public function version() { return $this->magic['v']; } //----- /** * Returns the $path property * * @return string */ public function path() { return $this->fspace->path(); } //----- /** * Get a section's content * * @param string $name The section name * @return string The section's content * @throws \Exception if section does not exist or cannot be read */ public function section($name) { try { $node=$this->stree->lookupFile($name); } catch (\Exception $e) { throw new \Exception($name.': Unknown section'); } try { return $node->read(); } catch (\Exception $e) { throw new \Exception($name.': Cannot read section - '.$e->getMessage()); } } //----- public function ftree() { return $this->ftree; } //----- public function stree() { return $this->stree; } //----- /** * Returns the $flags property * * @return integer */ public function flags() { return $this->flags; } //--------------------------------- public function displayPackages() { $this->ftree->displayPackages(); } //--------------------------------- // Display the file tree public function showfiles() { $this->ftree->display(true); } //--- } // End of class //=========================================================================== } // End of class_exists //=========================================================================== } // End of namespace //=========================================================================== ?>