PHP_Archive
[ class tree: PHP_Archive ] [ index: PHP_Archive ] [ all elements ]

Source for file Archive.php

Documentation is available at Archive.php

  1. <?php
  2. /**
  3.  * PHP_Archive Class (implements .phar)
  4.  *
  5.  * @package PHP_Archive
  6.  * @category PHP
  7.  */
  8. /**
  9.  * PHP_Archive Class (implements .phar)
  10.  *
  11.  * PHAR files a singular archive from which an entire application can run.
  12.  * To use it, simply package it using {@see PHP_Archive_Creator} and use phar://
  13.  * URIs to your includes. i.e. require_once 'phar://config.php' will include config.php
  14.  * from the root of the PHAR file.
  15.  *
  16.  * Gz code borrowed from the excellent File_Archive package by Vincent Lascaux.
  17.  *
  18.  * @copyright Copyright David Shafik and Synaptic Media 2004. All rights reserved.
  19.  * @author Davey Shafik <davey@synapticmedia.net>
  20.  * @author Greg Beaver <cellog@php.net>
  21.  * @link http://www.synapticmedia.net Synaptic Media
  22.  * @version $Id: Archive.php,v 1.52 2007/09/01 20:28:14 cellog Exp $
  23.  * @package PHP_Archive
  24.  * @category PHP
  25.  */
  26.  
  27. {
  28.     const GZ = 0x00001000;
  29.     const BZ2 = 0x00002000;
  30.     const SIG = 0x00010000;
  31.     const SHA1 = 0x0002;
  32.     const MD5 = 0x0001;
  33.     /**
  34.      * Whether this archive is compressed with zlib
  35.      *
  36.      * @var bool 
  37.      */
  38.     private $_compressed;
  39.     /**
  40.      * @var string Real path to the .phar archive
  41.      */
  42.     private $_archiveName = null;
  43.     /**
  44.      * Current file name in the phar
  45.      * @var string 
  46.      */
  47.     protected $currentFilename = null;
  48.     /**
  49.      * Length of current file in the phar
  50.      * @var string 
  51.      */
  52.     protected $internalFileLength = null;
  53.     /**
  54.      * Current file statistics (size, creation date, etc.)
  55.      * @var string 
  56.      */
  57.     protected $currentStat = null;
  58.     /**
  59.      * @var resource|nullPointer to open .phar
  60.      */
  61.     protected $fp = null;
  62.     /**
  63.      * @var int Current Position of the pointer
  64.      */
  65.     protected $position = 0;
  66.  
  67.     /**
  68.      * Map actual realpath of phars to meta-data about the phar
  69.      *
  70.      * Data is indexed by the alias that is used by internal files.  In other
  71.      * words, if a file is included via:
  72.      * <code>
  73.      * require_once 'phar://PEAR.phar/PEAR/Installer.php';
  74.      * </code>
  75.      * then the alias is "PEAR.phar"
  76.      * 
  77.      * Information stored is a boolean indicating whether this .phar is compressed
  78.      * with zlib, another for bzip2, phar-specific meta-data, and
  79.      * the precise offset of internal files
  80.      * within the .phar, used with the {@link $_manifest} to load actual file contents
  81.      * @var array 
  82.      */
  83.     private static $_pharMapping = array();
  84.     /**
  85.      * Map real file paths to alias used
  86.      *
  87.      * @var array 
  88.      */
  89.     private static $_pharFiles = array();
  90.     /**
  91.      * File listing for the .phar
  92.      * 
  93.      * The manifest is indexed per phar.
  94.      * 
  95.      * Files within the .phar are indexed by their relative path within the
  96.      * .phar.  Each file has this information in its internal array
  97.      *
  98.      * - 0 = uncompressed file size
  99.      * - 1 = timestamp of when file was added to phar
  100.      * - 2 = offset of file within phar relative to internal file's start
  101.      * - 3 = compressed file size (actual size in the phar)
  102.      * @var array 
  103.      */
  104.     private static $_manifest = array();
  105.     /**
  106.      * Absolute offset of internal files within the .phar, indexed by absolute
  107.      * path to the .phar
  108.      *
  109.      * @var array 
  110.      */
  111.     private static $_fileStart = array();
  112.     /**
  113.      * file name of the phar
  114.      *
  115.      * @var string 
  116.      */
  117.     private $_basename;
  118.  
  119.  
  120.     /**
  121.      * Default MIME types used for the web front controller
  122.      *
  123.      * @var array 
  124.      */
  125.     public static $defaultmimes = array(
  126.             'aif' => 'audio/x-aiff',
  127.             'aiff' => 'audio/x-aiff',
  128.             'arc' => 'application/octet-stream',
  129.             'arj' => 'application/octet-stream',
  130.             'art' => 'image/x-jg',
  131.             'asf' => 'video/x-ms-asf',
  132.             'asx' => 'video/x-ms-asf',
  133.             'avi' => 'video/avi',
  134.             'bin' => 'application/octet-stream',
  135.             'bm' => 'image/bmp',
  136.             'bmp' => 'image/bmp',
  137.             'bz2' => 'application/x-bzip2',
  138.             'css' => 'text/css',
  139.             'doc' => 'application/msword',
  140.             'dot' => 'application/msword',
  141.             'dv' => 'video/x-dv',
  142.             'dvi' => 'application/x-dvi',
  143.             'eps' => 'application/postscript',
  144.             'exe' => 'application/octet-stream',
  145.             'gif' => 'image/gif',
  146.             'gz' => 'application/x-gzip',
  147.             'gzip' => 'application/x-gzip',
  148.             'htm' => 'text/html',
  149.             'html' => 'text/html',
  150.             'ico' => 'image/x-icon',
  151.             'jpe' => 'image/jpeg',
  152.             'jpg' => 'image/jpeg',
  153.             'jpeg' => 'image/jpeg',
  154.             'js' => 'application/x-javascript',
  155.             'log' => 'text/plain',
  156.             'mid' => 'audio/x-midi',
  157.             'mov' => 'video/quicktime',
  158.             'mp2' => 'audio/mpeg',
  159.             'mp3' => 'audio/mpeg3',
  160.             'mpg' => 'audio/mpeg',
  161.             'pdf' => 'aplication/pdf',
  162.             'png' => 'image/png',
  163.             'rtf' => 'application/rtf',
  164.             'tif' => 'image/tiff',
  165.             'tiff' => 'image/tiff',
  166.             'txt' => 'text/plain',
  167.             'xml' => 'text/xml',
  168.         );
  169.  
  170.     public static $defaultphp = array(
  171.         'php' => true
  172.         );
  173.  
  174.     public static $defaultphps = array(
  175.         'phps' => true
  176.         );
  177.  
  178.     public static $deny = array('/.+\.inc$/');
  179.  
  180.     public static function viewSource($archive$file)
  181.     {
  182.         // security, idea borrowed from PHK
  183.         if (!file_exists($archive '.introspect')) {
  184.             header("HTTP/1.0 404 Not Found");
  185.             return false;
  186.         }
  187.         if (self::_fileExists($archive$_GET['viewsource'])) {
  188.             $source highlight_file('phar://@ALIAS@/' .
  189.                 $_GET['viewsource']true);
  190.             header('Content-Type: text/html');
  191.             header('Content-Length: ' strlen($source));
  192.             echo '<html><head><title>Source of ',
  193.                 htmlspecialchars($_GET['viewsource'])'</title></head>';
  194.             echo '<body><h1>Source of ',
  195.                 htmlspecialchars($_GET['viewsource'])'</h1>';
  196.             if (isset($_GET['introspect'])) {
  197.                 echo '<a href="'htmlspecialchars($_SERVER['PHP_SELF']),
  198.                     '?introspect='urlencode(htmlspecialchars($_GET['introspect'])),
  199.                     '">Return to 'htmlspecialchars($_GET['introspect'])'</a><br />';
  200.             }
  201.             echo $source;
  202.             return false;
  203.         else {
  204.             header("HTTP/1.0 404 Not Found");
  205.             return false;
  206.         }
  207.         
  208.     }
  209.  
  210.     public static function introspect($archive$dir)
  211.     {
  212.         // security, idea borrowed from PHK
  213.         if (!file_exists($archive '.introspect')) {
  214.             header("HTTP/1.0 404 Not Found");
  215.             return false;
  216.         }
  217.         if (!$dir{
  218.             $dir '/';
  219.         }
  220.         $dir = self::processFile($dir);
  221.         if ($dir[0!= '/'{
  222.             $dir '/' $dir;
  223.         }
  224.         try {
  225.             $self htmlspecialchars($_SERVER['PHP_SELF']);
  226.             $iterate = new DirectoryIterator('phar://@ALIAS@' $dir);
  227.             echo '<html><head><title>Introspect 'htmlspecialchars($dir),
  228.                 '</title></head><body><h1>Introspect 'htmlspecialchars($dir),
  229.                 '</h1><ul>';
  230.             if ($dir != '/'{
  231.                 echo '<li><a href="'$self'?introspect=',
  232.                     htmlspecialchars(dirname($dir))'">..</a></li>';
  233.             }
  234.             foreach ($iterate as $entry{
  235.                 if ($entry->isDot()) continue;
  236.                 $name = self::processFile($entry->getPathname());
  237.                 $name str_replace('phar://@ALIAS@/'''$name);
  238.                 if ($entry->isDir()) {
  239.                     echo '<li><a href="'$self'?introspect=',
  240.                         urlencode(htmlspecialchars($name)),
  241.                         '">',
  242.                         htmlspecialchars($entry->getFilename())'/</a> [directory]</li>';
  243.                 else {
  244.                     echo '<li><a href="'$self'?introspect=',
  245.                         urlencode(htmlspecialchars($dir))'&viewsource=',
  246.                         urlencode(htmlspecialchars($name)),
  247.                         '">',
  248.                         htmlspecialchars($entry->getFilename())'</a></li>';
  249.                 }
  250.             }
  251.             return false;
  252.         catch (Exception $e{
  253.             echo '<html><head><title>Directory not found: ',
  254.                 htmlspecialchars($dir)'</title></head>',
  255.                 '<body><h1>Directory not found: 'htmlspecialchars($dir)'</h1>',
  256.                 '<p>Try <a href="'htmlspecialchars($_SERVER['PHP_SELF'])'?introspect=/">',
  257.                 'This link</a></p></body></html>';
  258.             return false;
  259.         }
  260.     }
  261.  
  262.     public static function webFrontController($initfile)
  263.     {
  264.         if (isset($_SERVER&& isset($_SERVER['REQUEST_URI'])) {
  265.             $uri parse_url($_SERVER['REQUEST_URI']);
  266.             $archive realpath($_SERVER['SCRIPT_FILENAME']);
  267.             $subpath str_replace('/' basename($archive)''$uri['path']);
  268.             if (!$subpath || $subpath == '/'{
  269.                 if (isset($_GET['viewsource'])) {
  270.                     return self::viewSource($archive$_GET['viewsource']);
  271.                 }
  272.                 if (isset($_GET['introspect'])) {
  273.                     return self::introspect($archive$_GET['introspect']);
  274.                 }
  275.                 $subpath '/' $initfile;
  276.             }
  277.             if (!self::_fileExists($archivesubstr($subpath1))) {
  278.                 header("HTTP/1.0 404 Not Found");
  279.                 return false;
  280.             }
  281.             foreach (self::$deny as $pattern{
  282.                 if (preg_match($pattern$subpath)) {
  283.                     header("HTTP/1.0 404 Not Found");
  284.                     return false;
  285.                 }
  286.             }
  287.             $inf pathinfo(basename($subpath));
  288.             if (!isset($inf['extension'])) {
  289.                 header('Content-Type: text/plain');
  290.                 header('Content-Length: ' .
  291.                     self::_filesize($archivesubstr($subpath1)));
  292.                 readfile('phar://@ALIAS@' $subpath);
  293.                 return false;
  294.             }
  295.             if (isset(self::$defaultphp[$inf['extension']])) {
  296.                 include 'phar://@ALIAS@' $subpath;
  297.                 return false;
  298.             }
  299.             if (isset(self::$defaultmimes[$inf['extension']])) {
  300.                 header('Content-Type: ' . self::$defaultmimes[$inf['extension']]);
  301.                 header('Content-Length: ' .
  302.                     self::_filesize($archivesubstr($subpath1)));
  303.                 readfile('phar://@ALIAS@' $subpath);
  304.                 return false;
  305.             }
  306.             if (isset(self::$defaultphps[$inf['extension']])) {
  307.                 header('Content-Type: text/html');
  308.                 $c highlight_file('phar://@ALIAS@' $subpathtrue);
  309.                 header('Content-Length: ' strlen($c));
  310.                 echo $c;
  311.                 return false;
  312.             }
  313.             header('Content-Type: text/plain');
  314.             header('Content-Length: ' .
  315.                     self::_filesize($archivesubstr($subpath1)));
  316.             readfile('phar://@ALIAS@' $subpath);
  317.         }
  318.     }
  319.  
  320.     /**
  321.      * Detect end of stub
  322.      *
  323.      * @param string $buffer stub past '__HALT_'.'COMPILER();'
  324.      * @return end of stub, prior to length of manifest.
  325.      */
  326.     private static final function _endOfStubLength($buffer)
  327.     {
  328.         $pos = 0;
  329.         if (!strlen($buffer)) {
  330.             return $pos;
  331.         }
  332.         if (($buffer[0== ' ' || $buffer[0== "\n"&& @substr($buffer12== '?>')
  333.         {
  334.             $pos += 3;
  335.             if ($buffer[$pos== "\r" && $buffer[$pos+1== "\n"{
  336.                 $pos += 2;
  337.             }
  338.             else if ($buffer[$pos== "\n"{
  339.                 $pos += 1;
  340.             }
  341.         }
  342.         return $pos;
  343.     }
  344.  
  345.     /**
  346.      * Allows loading an external Phar archive without include()ing it
  347.      *
  348.      * @param string $file  phar package to load
  349.      * @param string $alias alias to use
  350.      * @throws Exception
  351.      */
  352.     public static final function loadPhar($file$alias = NULL)
  353.     {
  354.         $file realpath($file);
  355.         if ($file{
  356.             $fp fopen($file'rb');
  357.             $buffer '';
  358.             while (!feof($fp)) {
  359.                 $buffer .= fread($fp8192);
  360.                 // don't break phars
  361.                 if ($pos strpos($buffer'__HALT_COMPI' 'LER();')) {
  362.                     $buffer .= fread($fp5);
  363.                     fclose($fp);
  364.                     $pos += 18;
  365.                     $pos += self::_endOfStubLength(substr($buffer$pos));
  366.                     return self::_mapPhar($file$pos$alias);
  367.                 }
  368.             }
  369.             fclose($fp);
  370.         }
  371.     }
  372.  
  373.     /**
  374.      * Map a full real file path to an alias used to refer to the .phar
  375.      *
  376.      * This function can only be called from the initialization of the .phar itself.
  377.      * Any attempt to call from outside the .phar or to re-alias the .phar will fail
  378.      * as a security measure.
  379.      * @param string $alias 
  380.      * @param int $dataoffset the value of __COMPILER_HALT_OFFSET__
  381.      */
  382.     public static final function mapPhar($alias = NULL$dataoffset = NULL)
  383.     {
  384.         try {
  385.             $trace debug_backtrace();
  386.             $file $trace[0]['file'];
  387.             // this ensures that this is safe
  388.             if (!in_array($fileget_included_files())) {
  389.                 die('SECURITY ERROR: PHP_Archive::mapPhar can only be called from within ' .
  390.                     'the phar that initiates it');
  391.             }
  392.             $file realpath($file);
  393.             if (!isset($dataoffset)) {
  394.                 $dataoffset constant('__COMPILER_HALT_OFFSET'.'__');
  395.                 $fp fopen($file'rb');
  396.                 fseek($fp$dataoffsetSEEK_SET);
  397.                 $dataoffset $dataoffset + self::_endOfStubLength(fread($fp5));
  398.                 fclose($fp);
  399.             }
  400.  
  401.             self::_mapPhar($file$dataoffset);
  402.         catch (Exception $e{
  403.             die($e->getMessage());
  404.         }
  405.     }
  406.  
  407.     /**
  408.      * Sub-function, allows recovery from errors
  409.      *
  410.      * @param unknown_type $file 
  411.      * @param unknown_type $dataoffset 
  412.      */
  413.     private static function _mapPhar($file$dataoffset$alias = NULL)
  414.     {
  415.         $file realpath($file);
  416.         if (isset(self::$_manifest[$file])) {
  417.             return;
  418.         }
  419.         if (!is_array(self::$_pharMapping)) {
  420.             self::$_pharMapping = array();
  421.         }
  422.         $fp = fopen($file'rb');
  423.         // seek to __HALT_COMPILER_OFFSET__
  424.         fseek($fp$dataoffset);
  425.         $manifest_length unpack('Vlen'fread($fp4));
  426.         $manifest '';
  427.         $last '1';
  428.         while (strlen($last&& strlen($manifest$manifest_length['len']{
  429.             $read = 8192;
  430.             if ($manifest_length['len'strlen($manifest< 8192{
  431.                 $read $manifest_length['len'strlen($manifest);
  432.             }
  433.             $last fread($fp$read);
  434.             $manifest .= $last;
  435.         }
  436.         if (strlen($manifest$manifest_length['len']{
  437.             throw new Exception('ERROR: manifest length read was "' 
  438.                 strlen($manifest.'" should be "' .
  439.                 $manifest_length['len''"');
  440.         }
  441.         $info = self::_unserializeManifest($manifest);
  442.         if ($info['alias']{
  443.             $alias $info['alias'];
  444.             $explicit = true;
  445.         else {
  446.             if (!isset($alias)) {
  447.                 $alias $file;
  448.             }
  449.             $explicit = false;
  450.         }
  451.         self::$_manifest[$file$info['manifest'];
  452.         $compressed $info['compressed'];
  453.         self::$_fileStart[$file= ftell($fp);
  454.         fclose($fp);
  455.         if ($compressed 0x00001000{
  456.             if (!function_exists('gzinflate')) {
  457.                 throw new Exception('Error: zlib extension is not enabled - gzinflate() function needed' .
  458.                     ' for compressed .phars');
  459.             }
  460.         }
  461.         if ($compressed 0x00002000{
  462.             if (!function_exists('bzdecompress')) {
  463.                 throw new Exception('Error: bzip2 extension is not enabled - bzdecompress() function needed' .
  464.                     ' for compressed .phars');
  465.             }
  466.         }
  467.         if (isset(self::$_pharMapping[$alias])) {
  468.             throw new Exception('ERROR: PHP_Archive::mapPhar has already been called for alias "' .
  469.                 $alias '" cannot re-alias to "' $file '"');
  470.         }
  471.         self::$_pharMapping[$alias= array($file$compressed$dataoffset$explicit,
  472.             $info['metadata']);
  473.         self::$_pharFiles[$file$alias;
  474.     }
  475.  
  476.     /**
  477.      * extract the manifest into an internal array
  478.      *
  479.      * @param string $manifest 
  480.      * @return false|array
  481.      */
  482.     private static function _unserializeManifest($manifest)
  483.     {
  484.         // retrieve the number of files in the manifest
  485.         $info = unpack('V'substr($manifest04));
  486.         $apiver substr($manifest42);
  487.         $apiver bin2hex($apiver);
  488.         $apiver_dots hexdec($apiver[0]'.' hexdec($apiver[1]'.' hexdec($apiver[2]);
  489.         $majorcompat hexdec($apiver[0]);
  490.         $calcapi explode('.'self::APIVersion());
  491.         if ($calcapi[0!= $majorcompat{
  492.             throw new Exception('Phar is incompatible API version ' $apiver_dots ', but ' .
  493.                 'PHP_Archive is API version '.self::APIVersion());
  494.         }
  495.         if ($calcapi[0=== '0'{
  496.             if (self::APIVersion(!= $apiver_dots{
  497.                 throw new Exception('Phar is API version ' $apiver_dots .
  498.                     ', but PHP_Archive is API version '.self::APIVersion()E_USER_ERROR);
  499.             }
  500.         }
  501.         $flags unpack('V'substr($manifest64));
  502.         $ret = array('compressed' => $flags 0x00003000);
  503.         // signature is not verified by default in PHP_Archive, phar is better
  504.         $ret['hassignature'$flags 0x00010000;
  505.         $aliaslen unpack('V'substr($manifest104));
  506.         if ($aliaslen{
  507.             $ret['alias'substr($manifest14$aliaslen[1]);
  508.         else {
  509.             $ret['alias'= false;
  510.         }
  511.         $manifest substr($manifest14 + $aliaslen[1]);
  512.         $metadatalen unpack('V'substr($manifest04));
  513.         if ($metadatalen[1]{
  514.             $ret['metadata'unserialize(substr($manifest4$metadatalen[1]));
  515.             $manifest substr($manifest4 + $metadatalen[1]);
  516.         else {
  517.             $ret['metadata'= null;
  518.             $manifest substr($manifest4);
  519.         }
  520.         $offset = 0;
  521.         $start = 0;
  522.         for ($i = 0; $i $info[1]$i++{
  523.             // length of the file name
  524.             $len unpack('V'substr($manifest$start4));
  525.             $start += 4;
  526.             // file name
  527.             $savepath substr($manifest$start$len[1]);
  528.             $start += $len[1];
  529.             // retrieve manifest data:
  530.             // 0 = uncompressed file size
  531.             // 1 = timestamp of when file was added to phar
  532.             // 2 = compressed filesize
  533.             // 3 = crc32
  534.             // 4 = flags
  535.             // 5 = metadata length
  536.             $ret['manifest'][$savepatharray_values(unpack('Va/Vb/Vc/Vd/Ve/Vf'substr($manifest$start24)));
  537.             $ret['manifest'][$savepath][3sprintf('%u'$ret['manifest'][$savepath][3]
  538.                 0xffffffff);
  539.             if ($ret['manifest'][$savepath][5]{
  540.                 $ret['manifest'][$savepath][6unserialize(substr($manifest$start + 24,
  541.                     $ret['manifest'][$savepath][5]));
  542.             else {
  543.                 $ret['manifest'][$savepath][6= null;
  544.             }
  545.             $ret['manifest'][$savepath][7$offset;
  546.             $offset += $ret['manifest'][$savepath][2];
  547.             $start += 24 + $ret['manifest'][$savepath][5];
  548.         }
  549.         return $ret;
  550.     }
  551.  
  552.     /**
  553.      * @param string 
  554.      */
  555.     private static function processFile($path)
  556.     {
  557.         if ($path == '.'{
  558.             return '';
  559.         }
  560.         $std str_replace("\\""/"$path);
  561.         while ($std != ($std ereg_replace("[^\/:?]+/\.\./"""$std))) ;
  562.         $std str_replace("/./"""$std);
  563.         if (strlen($std> 1 && $std[0== '/'{
  564.             $std substr($std1);
  565.         }
  566.         if (strncmp($std"./"2== 0{
  567.             return substr($std2);
  568.         else {
  569.             return $std;
  570.         }
  571.     }
  572.  
  573.     /**
  574.      * Seek in the master archive to a matching file or directory
  575.      * @param string 
  576.      */
  577.     protected function selectFile($path$allowdirs = true)
  578.     {
  579.         $std = self::processFile($path);
  580.         if (isset(self::$_manifest[$this->_archiveName][$path])) {
  581.             $this->_setCurrentFile($path);
  582.             return true;
  583.         }
  584.         if (!$allowdirs{
  585.             return 'Error: "' $path '" is not a file in phar "' $this->_basename '"';
  586.         }
  587.         foreach (self::$_manifest[$this->_archiveNameas $file => $info{
  588.             if (empty($std||
  589.                   //$std is a directory
  590.                   strncmp($std.'/'$pathstrlen($std)+1== 0{
  591.                 $this->currentFilename = $this->internalFileLength = $this->currentStat = null;
  592.                 return true;
  593.             }
  594.         }
  595.         return 'Error: "' $path '" not found in phar "' $this->_basename '"';
  596.     }
  597.  
  598.     private function _setCurrentFile($path)
  599.     {
  600.         $this->currentStat = array(
  601.             2 => 0100444// file mode, readable by all, writeable by none
  602.             4 => 0// uid
  603.             5 => 0// gid
  604.             7 => self::$_manifest[$this->_archiveName][$path][0]// size
  605.             9 => self::$_manifest[$this->_archiveName][$path][1]// creation time
  606.             );
  607.         $this->currentFilename = $path;
  608.         $this->internalFileLength = self::$_manifest[$this->_archiveName][$path][2];
  609.         // seek to offset of file header within the .phar
  610.         if (is_resource(@$this->fp)) {
  611.             fseek($this->fpself::$_fileStart[$this->_archiveName+ self::$_manifest[$this->_archiveName][$path][7]);
  612.         }
  613.     }
  614.  
  615.     private static function _fileExists($archive$path)
  616.     {
  617.         return isset(self::$_manifest[$archive]&&
  618.             isset(self::$_manifest[$archive][$path]);
  619.     }
  620.  
  621.     private static function _filesize($archive$path)
  622.     {
  623.         return self::$_manifest[$archive][$path][0];
  624.     }
  625.  
  626.     /**
  627.      * Seek to a file within the master archive, and extract its contents
  628.      * @param string 
  629.      * @return array|stringan array containing an error message string is returned
  630.      *                       upon error, otherwise the file contents are returned
  631.      */
  632.     public function extractFile($path)
  633.     {
  634.         $this->fp = @fopen($this->_archiveName"rb");
  635.         if (!$this->fp{
  636.             return array('Error: cannot open phar "' $this->_archiveName '"');
  637.         }
  638.         if (($e $this->selectFile($pathfalse)) === true{
  639.             $data '';
  640.             $count $this->internalFileLength;
  641.             while ($count{
  642.                 if ($count < 8192{
  643.                     $data .= @fread($this->fp$count);
  644.                     $count = 0;
  645.                 else {
  646.                     $count -= 8192;
  647.                     $data .= @fread($this->fp8192);
  648.                 }
  649.             }
  650.             @fclose($this->fp);
  651.             if (self::$_manifest[$this->_archiveName][$path][4self::GZ{
  652.                 $data gzinflate($data);
  653.             elseif (self::$_manifest[$this->_archiveName][$path][4self::BZ2{
  654.                 $data = bzdecompress($data);
  655.             }
  656.             if (!isset(self::$_manifest[$this->_archiveName][$path]['ok'])) {
  657.                 if (strlen($data!= $this->currentStat[7]{
  658.                     return array("Not valid internal .phar file (size error {$size} != " .
  659.                         $this->currentStat[7")");
  660.                 }
  661.                 if (self::$_manifest[$this->_archiveName][$path][3!= sprintf("%u"crc32($data0xffffffff)) {
  662.                     return array("Not valid internal .phar file (checksum error)");
  663.                 }
  664.                 self::$_manifest[$this->_archiveName][$path]['ok'= true;
  665.             }
  666.             return $data;
  667.         else {
  668.             @fclose($this->fp);
  669.             return array($e);
  670.         }
  671.     }
  672.  
  673.     /**
  674.      * Parse urls like phar:///fullpath/to/my.phar/file.txt
  675.      *
  676.      * @param string $file 
  677.      * @return false|array
  678.      */
  679.     static protected function parseUrl($file)
  680.     {
  681.         if (substr($file07!= 'phar://'{
  682.             return false;
  683.         }
  684.         $file substr($file7);
  685.     
  686.         $ret = array('scheme' => 'phar');
  687.         $pos_p strpos($file'.phar.php');
  688.         $pos_z strpos($file'.phar.gz');
  689.         $pos_b strpos($file'.phar.bz2');
  690.         if ($pos_p{
  691.             if ($pos_z{
  692.                 return false;
  693.             }
  694.             $ret['host'substr($file0$pos_p strlen('.phar.php'));
  695.             $ret['path'substr($filestrlen($ret['host']));
  696.         elseif ($pos_z{
  697.             $ret['host'substr($file0$pos_z strlen('.phar.gz'));
  698.             $ret['path'substr($filestrlen($ret['host']));
  699.         elseif ($pos_b{
  700.             $ret['host'substr($file0$pos_z strlen('.phar.bz2'));
  701.             $ret['path'substr($filestrlen($ret['host']));
  702.         elseif (($pos_p strpos($file".phar")) !== false{
  703.             $ret['host'substr($file0$pos_p strlen('.phar'));
  704.             $ret['path'substr($filestrlen($ret['host']));
  705.         else {
  706.             return false;
  707.         }
  708.         if (!$ret['path']{
  709.             $ret['path''/';
  710.         }
  711.         return $ret;
  712.     }
  713.     
  714.     /**
  715.      * Locate the .phar archive in the include_path and detect the file to open within
  716.      * the archive.
  717.      *
  718.      * Possible parameters are phar://pharname.phar/filename_within_phar.ext
  719.      * @param string a file within the archive
  720.      * @return string the filename within the .phar to retrieve
  721.      */
  722.     public function initializeStream($file)
  723.     {
  724.         $file = self::processFile($file);
  725.         $info @parse_url($file);
  726.         if (!$info{
  727.             $info = self::parseUrl($file);
  728.         }
  729.         if (!$info{
  730.             return false;
  731.         }
  732.         if (!isset($info['host'])) {
  733.             // malformed internal file
  734.             return false;
  735.         }
  736.         if (!isset(self::$_pharFiles[$info['host']]&&
  737.               !isset(self::$_pharMapping[$info['host']])) {
  738.             try {
  739.                 self::loadPhar($info['host']);
  740.                 // use alias from here out
  741.                 $info['host'= self::$_pharFiles[$info['host']];
  742.             catch (Exception $e{
  743.                 return false;
  744.             }
  745.         }
  746.         if (!isset($info['path'])) {
  747.             return false;
  748.         elseif (strlen($info['path']> 1{
  749.             $info['path'substr($info['path']1);
  750.         }
  751.         if (isset(self::$_pharMapping[$info['host']])) {
  752.             $this->_basename $info['host'];
  753.             $this->_archiveName = self::$_pharMapping[$info['host']][0];
  754.             $this->_compressed = self::$_pharMapping[$info['host']][1];
  755.         elseif (isset(self::$_pharFiles[$info['host']])) {
  756.             $this->_archiveName $info['host'];
  757.             $this->_basename = self::$_pharFiles[$info['host']];
  758.             $this->_compressed = self::$_pharMapping[$this->_basename][1];
  759.         }
  760.         $file $info['path'];
  761.         return $file;
  762.     }
  763.  
  764.     /**
  765.      * Open the requested file - PHP streams API
  766.      *
  767.      * @param string $file String provided by the Stream wrapper
  768.      * @access private
  769.      */
  770.     public function stream_open($file)
  771.     {
  772.         return $this->_streamOpen($file);
  773.     }
  774.  
  775.     /**
  776.      * @param string filename to opne, or directory name
  777.      * @param bool if true, a directory will be matched, otherwise only files
  778.      *              will be matched
  779.      * @uses trigger_error()
  780.      * @return bool success of opening
  781.      * @access private
  782.      */
  783.     private function _streamOpen($file$searchForDir = false)
  784.     {
  785.         $path $this->initializeStream($file);
  786.         if (!$path{
  787.             trigger_error('Error: Unknown phar in "' $file '"'E_USER_ERROR);
  788.         }
  789.         if (is_array($this->file $this->extractFile($path))) {
  790.             trigger_error($this->file[0]E_USER_ERROR);
  791.             return false;
  792.         }
  793.         if ($path != $this->currentFilename{
  794.             if (!$searchForDir{
  795.                 trigger_error("Cannot open '$file', is a directory"E_USER_ERROR);
  796.                 return false;
  797.             else {
  798.                 $this->file '';
  799.                 return true;
  800.             }
  801.         }
  802.  
  803.         if (!is_null($this->file&& $this->file !== false{
  804.             return true;
  805.         else {
  806.             return false;
  807.         }
  808.     }
  809.     
  810.     /**
  811.      * Read the data - PHP streams API
  812.      *
  813.      * @param int 
  814.      * @access private
  815.      */
  816.     public function stream_read($count)
  817.     {
  818.         $ret substr($this->file$this->position$count);
  819.         $this->position += strlen($ret);
  820.         return $ret;
  821.     }
  822.     
  823.     /**
  824.      * Whether we've hit the end of the file - PHP streams API
  825.      * @access private
  826.      */
  827.     function stream_eof()
  828.     {
  829.         return $this->position >= $this->currentStat[7];
  830.     }
  831.     
  832.     /**
  833.      * For seeking the stream - PHP streams API
  834.      * @param int 
  835.      * @param SEEK_SET|SEEK_CUR|SEEK_END
  836.      * @access private
  837.      */
  838.     public function stream_seek($pos$whence)
  839.     {
  840.         switch ($whence{
  841.             case SEEK_SET:
  842.                 if ($pos < 0{
  843.                     return false;
  844.                 }
  845.                 $this->position = $pos;
  846.                 break;
  847.             case SEEK_CUR:
  848.                 if ($pos $this->currentStat[7< 0{
  849.                     return false;
  850.                 }
  851.                 $this->position += $pos;
  852.                 break;
  853.             case SEEK_END:
  854.                 if ($pos $this->currentStat[7< 0{
  855.                     return false;
  856.                 }
  857.                 $this->position = $pos $this->currentStat[7];
  858.                 break;
  859.             default:
  860.                 return false;
  861.         }
  862.         return true;
  863.     }
  864.     
  865.     /**
  866.      * The current position in the stream - PHP streams API
  867.      * @access private
  868.      */
  869.     public function stream_tell()
  870.     {
  871.         return $this->position;
  872.     }
  873.  
  874.     /**
  875.      * The result of an fstat call, returns mod time from creation, and file size -
  876.      * PHP streams API
  877.      * @uses _stream_stat()
  878.      * @access private
  879.      */
  880.     public function stream_stat()
  881.     {
  882.         return $this->_stream_stat();
  883.     }
  884.  
  885.     /**
  886.      * Retrieve statistics on a file or directory within the .phar
  887.      * @param string file/directory to stat
  888.      * @access private
  889.      */
  890.     public function _stream_stat($file = null)
  891.     {
  892.         $std $file ? self::processFile($file$this->currentFilename;
  893.         if ($file{
  894.             if (isset(self::$_manifest[$this->_archiveName][$file])) {
  895.                 $this->_setCurrentFile($file);
  896.                 $isdir = false;
  897.             else {
  898.                 do {
  899.                     $isdir = false;
  900.                     if ($file == '/'{
  901.                         break;
  902.                     }
  903.                     foreach (self::$_manifest[$this->_archiveNameas $path => $info{
  904.                         if (strpos($path$file=== 0{
  905.                             if (strlen($pathstrlen($file&&
  906.                                   $path[strlen($file)== '/'{
  907.                                 break 2;
  908.                             }
  909.                         }
  910.                     }
  911.                     // no files exist and no directories match this string
  912.                     return false;
  913.                 while (false);
  914.                 $isdir = true;
  915.             }
  916.         else {
  917.             $isdir = false; // open streams must be files
  918.         }
  919.         $mode $isdir ? 0040444 : 0100444;
  920.         // 040000 = dir, 010000 = file
  921.         // everything is readable, nothing is writeable
  922.         return array(
  923.            00$mode000000000// non-associative indices
  924.            'dev' => 0'ino' => 0,
  925.            'mode' => $mode,
  926.            'nlink' => 0'uid' => 0'gid' => 0'rdev' => 0'blksize' => 0'blocks' => 0,
  927.            'size' => $this->currentStat[7],
  928.            'atime' => $this->currentStat[9],
  929.            'mtime' => $this->currentStat[9],
  930.            'ctime' => $this->currentStat[9],
  931.            );
  932.     }
  933.  
  934.     /**
  935.      * Stat a closed file or directory - PHP streams API
  936.      * @param string 
  937.      * @param int 
  938.      * @access private
  939.      */
  940.     public function url_stat($url$flags)
  941.     {
  942.         $path $this->initializeStream($url);
  943.         return $this->_stream_stat($path);
  944.     }
  945.  
  946.     /**
  947.      * Open a directory in the .phar for reading - PHP streams API
  948.      * @param string directory name
  949.      * @access private
  950.      */
  951.     public function dir_opendir($path)
  952.     {
  953.         $info @parse_url($path);
  954.         if (!$info{
  955.             $info = self::parseUrl($path);
  956.             if (!$info{
  957.                 trigger_error('Error: "' $path '" is a file, and cannot be opened with opendir',
  958.                     E_USER_ERROR);
  959.                 return false;
  960.             }
  961.         }
  962.         $path !empty($info['path']?
  963.             $info['host'$info['path'$info['host''/';
  964.         $path $this->initializeStream('phar://' $path);
  965.         if (isset(self::$_manifest[$this->_archiveName][$path])) {
  966.             trigger_error('Error: "' $path '" is a file, and cannot be opened with opendir',
  967.                 E_USER_ERROR);
  968.             return false;
  969.         }
  970.         if ($path == false{
  971.             trigger_error('Error: Unknown phar in "' $file '"'E_USER_ERROR);
  972.             return false;
  973.         }
  974.         $this->fp = @fopen($this->_archiveName"rb");
  975.         if (!$this->fp{
  976.             trigger_error('Error: cannot open phar "' $this->_archiveName '"');
  977.             return false;
  978.         }
  979.         $this->_dirFiles = array();
  980.         foreach (self::$_manifest[$this->_archiveNameas $file => $info{
  981.             if ($path == '/'{
  982.                 if (strpos($file'/')) {
  983.                     $a explode('/'$file);
  984.                     $this->_dirFiles[array_shift($a)= true;
  985.                 else {
  986.                     $this->_dirFiles[$file= true;
  987.                 }
  988.             elseif (strpos($file$path=== 0{
  989.                 $fname substr($filestrlen($path+ 1);
  990.                 if (strpos($fname'/')) {
  991.                     // this is a directory
  992.                     $a explode('/'$fname);
  993.                     $this->_dirFiles[array_shift($a)= true;
  994.                 elseif ($file[strlen($path)== '/'{
  995.                     // this is a file
  996.                     $this->_dirFiles[$fname= true;
  997.                 }
  998.             }
  999.         }
  1000.         @fclose($this->fp);
  1001.         if (!count($this->_dirFiles)) {
  1002.             return false;
  1003.         }
  1004.         @uksort($this->_dirFiles'strnatcmp');
  1005.         return true;
  1006.     }
  1007.  
  1008.     /**
  1009.      * Read the next directory entry - PHP streams API
  1010.      * @access private
  1011.      */
  1012.     public function dir_readdir()
  1013.     {
  1014.         $ret key($this->_dirFiles);
  1015.         @next($this->_dirFiles);
  1016.         if (!$ret{
  1017.             return false;
  1018.         }
  1019.         return $ret;
  1020.     }
  1021.  
  1022.     /**
  1023.      * Close a directory handle opened with opendir() - PHP streams API
  1024.      * @access private
  1025.      */
  1026.     public function dir_closedir()
  1027.     {
  1028.         $this->_dirFiles = array();
  1029.         return true;
  1030.     }
  1031.  
  1032.     /**
  1033.      * Rewind to the first directory entry - PHP streams API
  1034.      * @access private
  1035.      */
  1036.     public function dir_rewinddir()
  1037.     {
  1038.         @reset($this->_dirFiles);
  1039.         return true;
  1040.     }
  1041.  
  1042.     /**
  1043.      * API version of this class
  1044.      * @return string 
  1045.      */
  1046.     public static final function APIVersion()
  1047.     {
  1048.         return '1.0.0';
  1049.     }
  1050.  
  1051.     /**
  1052.      * Retrieve Phar-specific metadata for a Phar archive
  1053.      *
  1054.      * @param string $phar full path to Phar archive, or alias
  1055.      * @return null|mixedThe value that was serialized for the Phar
  1056.      *                     archive's metadata
  1057.      * @throws Exception
  1058.      */
  1059.     public static function getPharMetadata($phar)
  1060.     {
  1061.         if (isset(self::$_pharFiles[$phar])) {
  1062.             $phar = self::$_pharFiles[$phar];
  1063.         }
  1064.         if (!isset(self::$_pharMapping[$phar])) {
  1065.             throw new Exception('Unknown Phar archive: "' $phar '"');
  1066.         }
  1067.         return self::$_pharMapping[$phar][4];
  1068.     }
  1069.  
  1070.     /**
  1071.      * Retrieve File-specific metadata for a Phar archive file
  1072.      *
  1073.      * @param string $phar full path to Phar archive, or alias
  1074.      * @param string $file relative path to file within Phar archive
  1075.      * @return null|mixedThe value that was serialized for the Phar
  1076.      *                     archive's metadata
  1077.      * @throws Exception
  1078.      */
  1079.     public static function getFileMetadata($phar$file)
  1080.     {
  1081.         if (!isset(self::$_pharFiles[$phar])) {
  1082.             if (!isset(self::$_pharMapping[$phar])) {
  1083.                 throw new Exception('Unknown Phar archive: "' $phar '"');
  1084.             }
  1085.             $phar = self::$_pharMapping[$phar][0];
  1086.         }
  1087.         if (!isset(self::$_manifest[$phar])) {
  1088.             throw new Exception('Unknown Phar: "' $phar '"');
  1089.         }
  1090.         $file = self::processFile($file);
  1091.         if (!isset(self::$_manifest[$phar][$file])) {
  1092.             throw new Exception('Unknown file "' $file '" within Phar "'$phar '"');
  1093.         }
  1094.         return self::$_manifest[$phar][$file][6];
  1095.     }
  1096.  
  1097.     /**
  1098.      * @return list of supported signature algorithmns.
  1099.      */
  1100.     public static function getsupportedsignatures()
  1101.     {
  1102.         $ret = array('MD5''SHA-1');
  1103.         if (extension_loaded('hash')) {
  1104.             $ret['SHA-256';
  1105.             $ret['SHA-512';
  1106.         }
  1107.         return $ret;
  1108.     }
  1109. }
  1110. ?>

Documentation generated on Mon, 19 May 2008 11:30:09 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.