#!/bin/env php PHK Manager - File: /src/PHK/Proxy.php
Package Home

PHK Manager

PHK Home

File: /src/PHK/Proxy.php

Size:13172
Storage flags:

<?php
//=============================================================================
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
//=============================================================================
/**
* @copyright Francois Laupretre <phk@tekwire.net>
* @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 '<?'.'php' or it will be translated when building the runtime code

if ($interp==='') return str_pad('<?'.'php',\PHK\Proxy::INTERP_LEN-2).'?'.'>';
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
//===========================================================================
?>

For more information about the PHK package format: http://phk.tekwire.net