Source for file Archive.php
Documentation is available at Archive.php
* PHP_Archive Class (implements .phar)
* PHP_Archive Class (implements .phar)
* PHAR files a singular archive from which an entire application can run.
* To use it, simply package it using {@see PHP_Archive_Creator} and use phar://
* URIs to your includes. i.e. require_once 'phar://config.php' will include config.php
* from the root of the PHAR file.
* Gz code borrowed from the excellent File_Archive package by Vincent Lascaux.
* @copyright Copyright David Shafik and Synaptic Media 2004. All rights reserved.
* @author Davey Shafik <davey@synapticmedia.net>
* @author Greg Beaver <cellog@php.net>
* @link http://www.synapticmedia.net Synaptic Media
* @version $Id: Archive.php,v 1.52 2007/09/01 20:28:14 cellog Exp $
* Whether this archive is compressed with zlib
* @var string Real path to the .phar archive
private $_archiveName = null;
* Current file name in the phar
* Length of current file in the phar
* Current file statistics (size, creation date, etc.)
* @var resource|nullPointer to open .phar
* @var int Current Position of the pointer
* Map actual realpath of phars to meta-data about the phar
* Data is indexed by the alias that is used by internal files. In other
* words, if a file is included via:
* require_once 'phar://PEAR.phar/PEAR/Installer.php';
* then the alias is "PEAR.phar"
* Information stored is a boolean indicating whether this .phar is compressed
* with zlib, another for bzip2, phar-specific meta-data, and
* the precise offset of internal files
* within the .phar, used with the {@link $_manifest} to load actual file contents
private static $_pharMapping = array ();
* Map real file paths to alias used
private static $_pharFiles = array ();
* File listing for the .phar
* The manifest is indexed per phar.
* Files within the .phar are indexed by their relative path within the
* .phar. Each file has this information in its internal array
* - 0 = uncompressed file size
* - 1 = timestamp of when file was added to phar
* - 2 = offset of file within phar relative to internal file's start
* - 3 = compressed file size (actual size in the phar)
private static $_manifest = array ();
* Absolute offset of internal files within the .phar, indexed by absolute
private static $_fileStart = array ();
* Default MIME types used for the web front controller
public static $defaultmimes = array (
'aiff' => 'audio/x-aiff',
'arc' => 'application/octet-stream',
'arj' => 'application/octet-stream',
'asf' => 'video/x-ms-asf',
'asx' => 'video/x-ms-asf',
'bin' => 'application/octet-stream',
'bz2' => 'application/x-bzip2',
'doc' => 'application/msword',
'dot' => 'application/msword',
'dvi' => 'application/x-dvi',
'eps' => 'application/postscript',
'exe' => 'application/octet-stream',
'gz' => 'application/x-gzip',
'gzip' => 'application/x-gzip',
'js' => 'application/x-javascript',
'mov' => 'video/quicktime',
'pdf' => 'aplication/pdf',
'rtf' => 'application/rtf',
public static $defaultphp = array (
public static $defaultphps = array (
public static $deny = array ('/.+\.inc$/');
public static function viewSource($archive, $file)
// security, idea borrowed from PHK
header("HTTP/1.0 404 Not Found");
if (self ::_fileExists ($archive, $_GET['viewsource'])) {
$_GET['viewsource'], true );
header('Content-Type: text/html');
echo '<html><head><title>Source of ',
echo '<body><h1>Source of ',
if (isset ($_GET['introspect'])) {
header("HTTP/1.0 404 Not Found");
// security, idea borrowed from PHK
header("HTTP/1.0 404 Not Found");
$dir = self ::processFile ($dir);
$iterate = new DirectoryIterator ('phar://@ALIAS@' . $dir);
echo '<li><a href="', $self, '?introspect=',
foreach ($iterate as $entry) {
if ($entry->isDot ()) continue;
$name = self ::processFile ($entry->getPathname ());
echo '<li><a href="', $self, '?introspect=',
echo '<li><a href="', $self, '?introspect=',
echo '<html><head><title>Directory not found: ',
'This link</a></p></body></html>';
if (isset ($_SERVER) && isset ($_SERVER['REQUEST_URI'])) {
$archive = realpath($_SERVER['SCRIPT_FILENAME']);
if (!$subpath || $subpath == '/') {
if (isset ($_GET['viewsource'])) {
return self ::viewSource ($archive, $_GET['viewsource']);
if (isset ($_GET['introspect'])) {
return self ::introspect ($archive, $_GET['introspect']);
$subpath = '/' . $initfile;
if (!self ::_fileExists ($archive, substr($subpath, 1 ))) {
header("HTTP/1.0 404 Not Found");
foreach (self ::$deny as $pattern) {
if (preg_match ($pattern, $subpath)) {
header("HTTP/1.0 404 Not Found");
if (!isset ($inf['extension'])) {
header('Content-Type: text/plain');
self ::_filesize ($archive, substr($subpath, 1 )));
if (isset (self ::$defaultphp[$inf['extension']])) {
include 'phar://@ALIAS@' . $subpath;
if (isset (self ::$defaultmimes[$inf['extension']])) {
header ('Content-Type: ' . self ::$defaultmimes[$inf['extension']]);
header ('Content-Length: ' .
self ::_filesize ($archive, substr($subpath, 1 )));
if (isset (self ::$defaultphps[$inf['extension']])) {
header ('Content-Type: text/html');
header('Content-Type: text/plain');
self ::_filesize ($archive, substr($subpath, 1 )));
* @param string $buffer stub past '__HALT_'.'COMPILER();'
* @return end of stub, prior to length of manifest.
private static final function _endOfStubLength ($buffer)
if (($buffer[0 ] == ' ' || $buffer[0 ] == "\n") && @substr($buffer, 1 , 2 ) == '?>')
if ($buffer[$pos] == "\r" && $buffer[$pos+1 ] == "\n") {
else if ($buffer[$pos] == "\n") {
* Allows loading an external Phar archive without include()ing it
* @param string $file phar package to load
* @param string $alias alias to use
public static final function loadPhar($file, $alias = NULL )
$fp = fopen($file, 'rb');
$buffer .= fread($fp, 8192 );
if ($pos = strpos($buffer, '__HALT_COMPI' . 'LER();')) {
$buffer .= fread($fp, 5 );
$pos += self ::_endOfStubLength (substr($buffer, $pos));
return self ::_mapPhar ($file, $pos, $alias);
* Map a full real file path to an alias used to refer to the .phar
* This function can only be called from the initialization of the .phar itself.
* Any attempt to call from outside the .phar or to re-alias the .phar will fail
* @param int $dataoffset the value of __COMPILER_HALT_OFFSET__
public static final function mapPhar($alias = NULL , $dataoffset = NULL )
$file = $trace[0 ]['file'];
// this ensures that this is safe
die ('SECURITY ERROR: PHP_Archive::mapPhar can only be called from within ' .
'the phar that initiates it');
if (!isset ($dataoffset)) {
$dataoffset = constant('__COMPILER_HALT_OFFSET'. '__');
$fp = fopen($file, 'rb');
fseek($fp, $dataoffset, SEEK_SET );
$dataoffset = $dataoffset + self ::_endOfStubLength (fread($fp, 5 ));
self ::_mapPhar ($file, $dataoffset);
* Sub-function, allows recovery from errors
* @param unknown_type $file
* @param unknown_type $dataoffset
private static function _mapPhar ($file, $dataoffset, $alias = NULL )
if (isset (self ::$_manifest[$file])) {
if (!is_array (self ::$_pharMapping)) {
self ::$_pharMapping = array ();
$fp = fopen ($file, 'rb');
// seek to __HALT_COMPILER_OFFSET__
while (strlen($last) && strlen($manifest) < $manifest_length['len']) {
if ($manifest_length['len'] - strlen($manifest) < 8192 ) {
$read = $manifest_length['len'] - strlen($manifest);
$last = fread($fp, $read);
if (strlen($manifest) < $manifest_length['len']) {
throw new Exception ('ERROR: manifest length read was "' .
strlen($manifest) . '" should be "' .
$manifest_length['len'] . '"');
$info = self ::_unserializeManifest ($manifest);
self ::$_manifest[$file] = $info['manifest'];
$compressed = $info['compressed'];
self ::$_fileStart[$file] = ftell ($fp);
if ($compressed & 0x00001000 ) {
throw new Exception ('Error: zlib extension is not enabled - gzinflate() function needed' .
' for compressed .phars');
if ($compressed & 0x00002000 ) {
throw new Exception ('Error: bzip2 extension is not enabled - bzdecompress() function needed' .
' for compressed .phars');
if (isset (self ::$_pharMapping[$alias])) {
throw new Exception ('ERROR: PHP_Archive::mapPhar has already been called for alias "' .
$alias . '" cannot re-alias to "' . $file . '"');
self ::$_pharMapping[$alias] = array ($file, $compressed, $dataoffset, $explicit,
self ::$_pharFiles[$file] = $alias;
* extract the manifest into an internal array
* @param string $manifest
private static function _unserializeManifest ($manifest)
// retrieve the number of files in the manifest
$info = unpack ('V', substr($manifest, 0 , 4 ));
$apiver = substr($manifest, 4 , 2 );
$majorcompat = hexdec($apiver[0 ]);
$calcapi = explode('.', self ::APIVersion ());
if ($calcapi[0 ] != $majorcompat) {
throw new Exception ('Phar is incompatible API version ' . $apiver_dots . ', but ' .
'PHP_Archive is API version '.self ::APIVersion ());
if ($calcapi[0 ] === '0') {
if (self ::APIVersion () != $apiver_dots) {
throw new Exception ('Phar is API version ' . $apiver_dots .
', but PHP_Archive is API version '.self ::APIVersion (), E_USER_ERROR );
$ret = array ('compressed' => $flags & 0x00003000 );
// signature is not verified by default in PHP_Archive, phar is better
$ret['hassignature'] = $flags & 0x00010000;
$ret['alias'] = substr($manifest, 14 , $aliaslen[1 ]);
$manifest = substr($manifest, 14 + $aliaslen[1 ]);
$manifest = substr($manifest, 4 + $metadatalen[1 ]);
$manifest = substr($manifest, 4 );
for ($i = 0; $i < $info[1 ]; $i++ ) {
// length of the file name
$savepath = substr($manifest, $start, $len[1 ]);
// retrieve manifest data:
// 0 = uncompressed file size
// 1 = timestamp of when file was added to phar
// 2 = compressed filesize
$ret['manifest'][$savepath][3 ] = sprintf('%u', $ret['manifest'][$savepath][3 ]
if ($ret['manifest'][$savepath][5 ]) {
$ret['manifest'][$savepath][5 ]));
$ret['manifest'][$savepath][6 ] = null;
$ret['manifest'][$savepath][7 ] = $offset;
$offset += $ret['manifest'][$savepath][2 ];
$start += 24 + $ret['manifest'][$savepath][5 ];
private static function processFile ($path)
while ($std != ($std = ereg_replace("[^\/:?]+/\.\./", "", $std))) ;
if (strlen($std) > 1 && $std[0 ] == '/') {
* Seek in the master archive to a matching file or directory
protected function selectFile($path, $allowdirs = true )
$std = self ::processFile ($path);
if (isset (self ::$_manifest[$this->_archiveName][$path])) {
$this->_setCurrentFile ($path);
return 'Error: "' . $path . '" is not a file in phar "' . $this->_basename . '"';
foreach (self ::$_manifest[$this->_archiveName] as $file => $info) {
strncmp ($std. '/', $path, strlen($std)+1 ) == 0 ) {
return 'Error: "' . $path . '" not found in phar "' . $this->_basename . '"';
private function _setCurrentFile ($path)
2 => 0100444 , // file mode, readable by all, writeable by none
7 => self ::$_manifest[$this->_archiveName][$path][0 ], // size
9 => self ::$_manifest[$this->_archiveName][$path][1 ], // creation time
// seek to offset of file header within the .phar
if (is_resource (@$this->fp)) {
fseek($this->fp, self ::$_fileStart[$this->_archiveName] + self ::$_manifest[$this->_archiveName][$path][7 ]);
private static function _fileExists ($archive, $path)
return isset (self ::$_manifest[$archive]) &&
isset (self ::$_manifest[$archive][$path]);
private static function _filesize ($archive, $path)
return self ::$_manifest[$archive][$path][0 ];
* Seek to a file within the master archive, and extract its contents
* @return array|stringan array containing an error message string is returned
* upon error, otherwise the file contents are returned
$this->fp = @fopen ($this->_archiveName, "rb");
return array ('Error: cannot open phar "' . $this->_archiveName . '"');
if (($e = $this->selectFile($path, false )) === true ) {
$data .= @fread($this->fp, $count);
if (self ::$_manifest[$this->_archiveName][$path][4 ] & self ::GZ ) {
} elseif (self ::$_manifest[$this->_archiveName][$path][4 ] & self ::BZ2 ) {
$data = bzdecompress ($data);
if (!isset (self ::$_manifest[$this->_archiveName][$path]['ok'])) {
return array (" Not valid internal .phar file (size error {$size} != " .
if (self ::$_manifest[$this->_archiveName][$path][3 ] != sprintf ("%u", crc32($data) & 0xffffffff )) {
return array ("Not valid internal .phar file (checksum error)");
self ::$_manifest[$this->_archiveName][$path]['ok'] = true;
* Parse urls like phar:///fullpath/to/my.phar/file.txt
static protected function parseUrl($file)
if (substr($file, 0 , 7 ) != 'phar://') {
$ret = array ('scheme' => 'phar');
$pos_p = strpos($file, '.phar.php');
$pos_z = strpos($file, '.phar.gz');
$pos_b = strpos($file, '.phar.bz2');
$ret['host'] = substr($file, 0 , $pos_p + strlen('.phar.php'));
$ret['host'] = substr($file, 0 , $pos_z + strlen('.phar.gz'));
$ret['host'] = substr($file, 0 , $pos_z + strlen('.phar.bz2'));
} elseif (($pos_p = strpos($file, ".phar")) !== false ) {
* Locate the .phar archive in the include_path and detect the file to open within
* Possible parameters are phar://pharname.phar/filename_within_phar.ext
* @param string a file within the archive
* @return string the filename within the .phar to retrieve
$file = self ::processFile ($file);
$info = self ::parseUrl ($file);
if (!isset ($info['host'])) {
// malformed internal file
if (!isset (self ::$_pharFiles[$info['host']]) &&
!isset (self ::$_pharMapping[$info['host']])) {
self ::loadPhar ($info['host']);
// use alias from here out
$info['host'] = self ::$_pharFiles[$info['host']];
if (!isset ($info['path'])) {
} elseif (strlen($info['path']) > 1 ) {
$info['path'] = substr($info['path'], 1 );
if (isset (self ::$_pharMapping[$info['host']])) {
$this->_basename = $info['host'];
$this->_archiveName = self ::$_pharMapping[$info['host']][0 ];
$this->_compressed = self ::$_pharMapping[$info['host']][1 ];
} elseif (isset (self ::$_pharFiles[$info['host']])) {
$this->_archiveName = $info['host'];
$this->_basename = self ::$_pharFiles[$info['host']];
$this->_compressed = self ::$_pharMapping[$this->_basename][1 ];
* Open the requested file - PHP streams API
* @param string $file String provided by the Stream wrapper
return $this->_streamOpen ($file);
* @param string filename to opne, or directory name
* @param bool if true, a directory will be matched, otherwise only files
* @return bool success of opening
private function _streamOpen ($file, $searchForDir = false )
trigger_error ('Error: Unknown phar in "' . $file . '"', E_USER_ERROR );
trigger_error(" Cannot open '$file', is a directory" , E_USER_ERROR );
if (!is_null($this->file) && $this->file !== false ) {
* Read the data - PHP streams API
* Whether we've hit the end of the file - PHP streams API
* For seeking the stream - PHP streams API
* @param SEEK_SET|SEEK_CUR|SEEK_END
* The current position in the stream - PHP streams API
* The result of an fstat call, returns mod time from creation, and file size -
* Retrieve statistics on a file or directory within the .phar
* @param string file/directory to stat
if (isset (self ::$_manifest[$this->_archiveName][$file])) {
$this->_setCurrentFile ($file);
foreach (self ::$_manifest[$this->_archiveName] as $path => $info) {
if (strpos ($path, $file) === 0 ) {
$path[strlen($file)] == '/') {
// no files exist and no directories match this string
$isdir = false; // open streams must be files
$mode = $isdir ? 0040444 : 0100444;
// 040000 = dir, 010000 = file
// everything is readable, nothing is writeable
0 , 0 , $mode, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , // non-associative indices
'nlink' => 0 , 'uid' => 0 , 'gid' => 0 , 'rdev' => 0 , 'blksize' => 0 , 'blocks' => 0 ,
* Stat a closed file or directory - PHP streams API
* Open a directory in the .phar for reading - PHP streams API
* @param string directory name
$info = self ::parseUrl ($path);
trigger_error('Error: "' . $path . '" is a file, and cannot be opened with opendir',
$path = !empty ($info['path']) ?
$info['host'] . $info['path'] : $info['host'] . '/';
if (isset (self ::$_manifest[$this->_archiveName][$path])) {
trigger_error ('Error: "' . $path . '" is a file, and cannot be opened with opendir',
trigger_error('Error: Unknown phar in "' . $file . '"', E_USER_ERROR );
$this->fp = @fopen($this->_archiveName, "rb");
trigger_error('Error: cannot open phar "' . $this->_archiveName . '"');
$this->_dirFiles = array ();
foreach (self ::$_manifest[$this->_archiveName] as $file => $info) {
if (strpos ($file, '/')) {
$this->_dirFiles[$file] = true;
} elseif (strpos($file, $path) === 0 ) {
} elseif ($file[strlen($path)] == '/') {
$this->_dirFiles[$fname] = true;
if (!count($this->_dirFiles)) {
@uksort($this->_dirFiles, 'strnatcmp');
* Read the next directory entry - PHP streams API
$ret = key($this->_dirFiles);
* Close a directory handle opened with opendir() - PHP streams API
$this->_dirFiles = array ();
* Rewind to the first directory entry - PHP streams API
@reset($this->_dirFiles);
* API version of this class
* Retrieve Phar-specific metadata for a Phar archive
* @param string $phar full path to Phar archive, or alias
* @return null|mixedThe value that was serialized for the Phar
if (isset (self ::$_pharFiles[$phar])) {
$phar = self ::$_pharFiles[$phar];
if (!isset (self ::$_pharMapping[$phar])) {
throw new Exception ('Unknown Phar archive: "' . $phar . '"');
return self ::$_pharMapping[$phar][4 ];
* Retrieve File-specific metadata for a Phar archive file
* @param string $phar full path to Phar archive, or alias
* @param string $file relative path to file within Phar archive
* @return null|mixedThe value that was serialized for the Phar
if (!isset (self ::$_pharFiles[$phar])) {
if (!isset (self ::$_pharMapping[$phar])) {
throw new Exception ('Unknown Phar archive: "' . $phar . '"');
$phar = self ::$_pharMapping[$phar][0 ];
if (!isset (self ::$_manifest[$phar])) {
throw new Exception ('Unknown Phar: "' . $phar . '"');
$file = self ::processFile ($file);
if (!isset (self ::$_manifest[$phar][$file])) {
throw new Exception ('Unknown file "' . $file . '" within Phar "'. $phar . '"');
return self ::$_manifest[$phar][$file][6 ];
* @return list of supported signature algorithmns.
$ret = array ('MD5', 'SHA-1');
if (extension_loaded ('hash')) {
Documentation generated on Mon, 19 May 2008 11:30:09 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.
|