// // 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 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 * @category PHK * @package PHK *///========================================================================== namespace PHK { if (!class_exists('PHK\Base',false)) { //============================================================================= /** * A mounted PHK archive file * * This file contains the front-end class for PHK object. This class gets its * data from the stream wrapper, which retrieves them from the cache or from * PHK\Proxy. This class is also used as base class for PHK\Build\Creator instances. * * This class does never access the package file directly. * * Note: When dealing with a sub-package, the mounted archive file is a 'phk://' * virtual file contained in a higher-level PHK archive (which, itself, can be * virtual). There is no limit to the nesting level of sub-packages. * * Runtime code -> 100% read-only * * PHK objects are created and destructed from the PHK manager (PHK\Mgr class). * * You get the PHK object instance by calling \PHK\Mgr::instance with a mount * point. This mount point is generally returned either by an include, * when including a package, or by \PHK\Mgr::uriToMnt(). * * @see \PHK\Mgr * @see \PHK\Proxy * * API status: Public * Included in the PHK PHP runtime: Yes * Implemented in the extension: Yes. Not used when extension is active. *///========================================================================== abstract class Base { //========== Class constants =============== /** Version. Checked against package's min runtime version */ /* Keep this value in sync with SOFTWARE_VERSION defined in make.common */ const RUNTIME_VERSION='3.0.0'; //----- // Mount flags /* The values defined here must be the same as in the accelerator extension */ /* PHK mount flags must not conflict with \Automap\Mgr::T_xx load flags as they can be */ /* combined. */ /** Mount flag - If set, force a CRC check when creating the PHK instance */ const CRC_CHECK=16; /** Mount flag - If set, don't call mount/umount scripts */ const NO_MOUNT_SCRIPT=32; /** Mount flag - If set, create a \PHK\Build\Creator object, instead of a PHK object */ const IS_CREATOR=64; //========== Class properties =============== /** @var bool Whether instance is valid or not (unmounted) */ private $valid; /** @var string Current mount point */ protected $mnt; /** @var string Parent mount point (for a subpackage) or null */ protected $parentMnt; /** @var array Package options */ protected $options=null; // Array /** @var array Build-time information */ protected $buildInfo=null; /** @var integer Mount flags */ protected $flags; /** @var string|null Automap load ID (if a map is present) */ protected $automapID; /** @var integer Package path (URI when subpackage) */ protected $path; /** @var Object|null Plugin object, if defined */ protected $plugin=null; /** @var boolean|null Allows to temporarily enable/disable caching */ protected $caching=null; /** @var int The modification time for every subfiles */ protected $mtime; /** @var \PHK\Backend The slow backend object (created only when needed) */ protected $backend=null; /** @var array File extension to mime type (constant) * * Would be cleaner if PHP class constants could contain arrays */ protected static $mimeTable=array( '' => 'text/plain', 'gif' => 'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'png' => 'image/png', 'psd' => 'image/psd', 'bmp' => 'image/bmp', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'iff' => 'image/iff', 'wbmp' => 'image/vnd.wap.wbmp', 'ico' => 'image/x-icon', 'xbm' => 'image/xbm', 'txt' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', 'css' => 'text/css', 'php' => 'application/x-httpd-php', 'phk' => 'application/x-httpd-php', 'inc' => 'application/x-httpd-php', 'hh' => 'application/x-httpd-php', 'pdf' => 'application/pdf', 'js' => 'application/x-javascript', 'swf' => 'application/x-shockwave-flash', 'xml' => 'application/xml', 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', 'mp3' => 'audio/mpeg', 'ram' => 'audio/x-pn-realaudio', 'svg' => 'image/svg+xml' ); //========== Class methods =============== // Methods to get read-only properties public function mnt() { $this->validate(); return $this->mnt; } public function flags() { $this->validate(); return $this->flags; } public function path() { $this->validate(); return $this->path; } public function mtime() { $this->validate(); return $this->mtime; } public function automapID() { $this->validate(); return $this->automapID; } public function options() { $this->validate(); return $this->options; } public function parentMnt() { $this->validate(); return $this->parentMnt; } public function plugin() { $this->validate(); return $this->plugin; } //----- public function __construct($parentMnt,$mnt,$path,$flags,$mtime) { $this->valid=true; $this->parentMnt=$parentMnt; $this->mnt=$mnt; $this->path=$path; $this->flags=$flags; $this->mtime=$mtime; } //----- public function validate() { if (!$this->valid) throw new \Exception("Accessing invalid or unmounted object"); } //----- public function init($options,$buildInfo) { try { $this->options=$options; $this->buildInfo=$buildInfo; $this->supportsPhpVersion(); if ($this->option('crc_check') || ($this->flags & self::CRC_CHECK)) $this->crcCheck(); // As required extensions are added to the enclosing package when a subpackage // is inserted, we don't have to check subpackages for required extensions. if (is_null($this->parentMnt)) { if (!is_null($extensions=$this->option('required_extensions'))) \PHK\Tools\Util::loadExtensions($extensions); } if ($this->mapDefined()) { // Transmit PHK mount flags to Automap $this->automapID=\Automap\Mgr::load($this->automapURI() ,$this->flags,$this->baseURI()); } else $this->automapID=0; //-- Call the mount script - if the mount script wants to refuse the mount, //-- it throws an exception. if (!($this->flags & \PHK::NO_MOUNT_SCRIPT) && (!is_null($mpath=$this->option('mount_script')))) { require $this->uri($mpath); } //-- Create the plugin_object if (!is_null($c=$this->option('plugin_class'))) $this->plugin=new $c($this->mnt); } catch (\Exception $e) { throw new \Exception('While initializing PHK instance - '.$e->getMessage()); } } //--------- public function mapDefined() { $this->validate(); if ($this->flags & \PHK::IS_CREATOR) return false; return $this->buildInfo('map_defined'); } //--------- public function setCache($toggle) { $this->validate(); $this->caching=$toggle; } //--------- /** * 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) { return \PHK\Proxy::fileIsPackage($path); } //--------- /** * Check if a data buffer contains a PHK package * * @param string $data data buffer to check * @return boolean */ public static function dataIsPackage($data) { return \PHK\Proxy::dataIsPackage($data); } //----- public function cacheEnabled($command,$params,$path) { $this->validate(); if ($this->flags & \PHK::IS_CREATOR) return false; if ($this->option('no_cache')===true) return false; if (!\PHK\Cache::cachePresent()) return false; if (!is_null($this->caching)) return $this->caching; return true; } //----- // Umount this entry. // We dont use __destruct because : // 1. We don't want this to be called on script shutdown // 2. Exceptions cannot be caught when sent from a destructor. public function umount() { $this->validate(); //-- Destroy the plugin if (!is_null($this->plugin)) unset($this->plugin); //-- Call the umount script if (!($this->flags & \PHK::NO_MOUNT_SCRIPT)) // Call the umount script { if (!is_null($upath=$this->option('umount_script'))) { require($this->uri($upath)); } } //-- Unload the automap if ($this->automapID) \Automap\Mgr::unload($this->automapID); $this->valid=false; } //----- public function uri($path) { $this->validate(); return \PHK\Mgr::uri($this->mnt,$path); } //----- public function sectionURI($section) { $this->validate(); return \PHK\Mgr::sectionURI($this->mnt,$section); } //----- public function commandURI($command) { $this->validate(); return \PHK\Mgr::commandURI($this->mnt,$command); } //----- public function baseURI() { $this->validate(); return \PHK\Mgr::baseURI($this->mnt); } //----- /** * Returns the URI of the map * * @return string */ public function automapURI() { $this->validate(); return \PHK\Mgr::automapURI($this->mnt); } //----- /** * Returns an option * * If the option is not set, returns null. * * The 'OPTIONS' section is mandatory in a package. * * @param string $key The option name * @return any|null Option value or null if the requested option is not set */ public function option($key) { $this->validate(); return (isset($this->options[$key]) ? $this->options[$key] : null); } //--------------------------------- public function webAccessAllowed($path) { $this->validate(); $plen=strlen($path); foreach(\PHK\Tools\Util::mkArray($this->option('web_access')) as $apath) { if ($apath=='/') return true; $alen=strlen($apath); if (($plen >= $alen) && (substr($path,0,$alen)==$apath) && (($alen==$plen)||($path{$alen}=='/'))) return true; } return false; } //--------------------------------- // Transfer control to main script (web mode) // Two methods: redirect or transparently execute main script. private function gotoMain($web_run_script) { if ($this->option('web_main_redirect')) { \PHK\Tools\Util::http301Redirect($web_run_script); } else return 'require(\''.$this->uri($web_run_script).'\');'; } //--------------------------------- // Returns the code to display or execute a subfile from the calling code. We // cannot directly include the subfile from this function because the variable // scope must be the calling one. // Use as : eval($phk->webTunnel([$path [,webinfo mode]])); // This function is supposed to transfer control in as transparent a manner as // possible. // If the given path is a directory, tries to find an index.[htm|html|php] file. // This function does not support subpaths in PHK subfiles. public function webTunnel($path=null,$webinfo=false) { $this->validate(); if (is_null($path)) $path=\PHK::setSubpath(); $last_slash=(substr($path,-1)=='/'); if ($path!='/') $path=rtrim($path,'/'); $web_run_script=$this->option('web_run_script'); $mnt=$this->mnt(); if ($path=='') { if (!is_null($web_run_script)) return $this->gotoMain($web_run_script); else \PHK\Tools\Util::http301Redirect('/'); // Redirect to the virtual root dir } // If a package use a path as both file and http paths, we can receive // a PHK URI. Handle this. Ugly: to be suppressed when PHP makes // current directory compatible with stream wrappers. // We check for one or two '/' between 'phk:' and $mnt because Apache removes // the 2nd '/'. // Suppressed in v 1.4.0: don't know if still useful ? //$path=str_replace('phk:/'.$mnt.'/','',$path); //$path=str_replace('phk://'.$mnt.'/','',$path); // Access enabled ? If not in the enabled paths, go to the main script // Allows to support a mod_rewrite-like feature where a single entry point // gets every request. if ((!$webinfo) && (!$this->webAccessAllowed($path)) && ($path!==$web_run_script)) { if (!is_null($web_run_script)) return $this->gotoMain($web_run_script); else \PHK\Tools\Util::http403Fail(); // Returns 'Forbidden' } // File exists ? $uri=$this->uri($path); if (($a=@stat($uri))===false) \PHK\Tools\Util::http404Fail(); if (($a['mode'] & 0170000) == 040000) // Special case for directory { $file_path=null; if ($last_slash) // Search a DirectoryIndex { foreach(array('index.htm', 'index.html', 'index.php') as $fname) { if (is_file($this->uri($path.'/'.$fname))) { $file_path=$path.'/'.$fname; break; } } if (is_null($file_path)) \PHK\Tools\Util::http404Fail(); // No Directory Index } else \PHK\Tools\Util::http301Redirect($path.'/'); } else $file_path=$path; // Now, we return the string which will be used by the calling environment // to execute the file if it is a PHP source, or to output its content // with the correct mime type. Execution is disabled in webinfo mode if ((!$webinfo) && ($this->isPHPSourcePath($file_path))) { return "require('".$this->uri($file_path)."');"; } else { return "\PHK\Mgr::instance('".$this->mnt."')->mimeHeader('$file_path');\n" ."readfile('".$this->uri($file_path)."');"; } } //--------------------------------- /** * Sends a mime header corresponding to a path * * Actually, we use only the file suffix (the path can correspond to an existing * node or not). * * If the suffix does not correspond to anything we know, nothing is sent * (defaults to text/html on Apache, don't know if it can change on another * SAPI). * * @param string $path * @return void */ public function mimeHeader($path) { $this->validate(); if (!is_null($type=$this->mimeType($path))) header('Content-type: '.$type); } //--------- /** * Returns the mime-type corresponding to a given path, or null if the * suffix does not correspond to anything we know * * Searches : * * 1. The 'mime-types' option * 2. The built-in mime table * 3. If the suffix contains 'php', sets the type to 'application/x-httpd-php' * * @param string $path * @return string|null The mime type or null if file suffix is unknown */ public function mimeType($path) { $this->validate(); $ext=\PHK\Tools\Util::fileSuffix($path); if ((!is_null($mtab=$this->option('mime_types'))) && isset($mtab[$ext])) return $mtab[$ext]; if (isset(self::$mimeTable[$ext])) return self::$mimeTable[$ext]; if (strpos($ext,'php')!==false) return 'application/x-httpd-php'; return null; } //--------- /** * Should we consider this path as a PHP source file ? * * In order to be identified as PHP source, a path must be associated with Mime * type 'application/x-httpd-php'. * * @param string $path * @return boolean */ public function isPHPSourcePath($path) { $this->validate(); return ($this->mimeType($path)==='application/x-httpd-php'); } //--------- public function proxy() { $this->validate(); return \PHK\Mgr::proxy($this->mnt); } //--------- /** * Checks the CRC of the PHK archive file * * Generates an exception if the check fails * * @return void * @throws \Exception */ public function crcCheck() { $this->validate(); $this->proxy()->crcCheck(); } //--------- private function supportsPhpVersion() { $this->validate(); if ((!is_null($minv=$this->option('min_php_version'))) && (version_compare(PHP_VERSION,$minv) < 0)) throw new \Exception("PHP minimum supported version: $minv (current is ".PHP_VERSION.")"); if ((!is_null($maxv=$this->option('max_php_version'))) && (version_compare(PHP_VERSION,$maxv) > 0)) throw new \Exception("PHP maximum supported version: $maxv (current is ".PHP_VERSION.")"); } //----- /** * Is the PHK accelerator in use or not ? * * @return boolean */ public static function acceleratorIsPresent() { return false; } //----- /** * Returns a build-time information field or the whole array * * Unlike options, an unknown key throws an error * * @param string|null $name Field name * @return array|string|null The field's content or null if it does not exist */ public function buildInfo($name=null) { $this->validate(); if (is_null($name)) return $this->buildInfo; if (!isset($this->buildInfo[$name])) throw new \Exception($name.': unknown build info'); return $this->buildInfo[$name]; } //--------------------------------- public static function subpathURL($path) { return \PHK\Backend::subpathURL($path); } //--------------------------------- // Get the sub-path from an URL. Because of the problems with CGI mode, we // have to support 2 syntaxes : // http:///.../ // http:///.../?_phk_path= public static function setSubpath() { $path=''; if (isset($_REQUEST['_phk_path'])) $path=urldecode($_REQUEST['_phk_path']); else { $path=isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : ''; if ($path=='' && isset($_SERVER['ORIG_PATH_INFO'])) $path=$_SERVER['ORIG_PATH_INFO']; } if (($path!='') && ($path{0}!='/')) $path='/'.$path; return $path; } //---- // Undocumented private function backend() { $this->validate(); if (is_null($this->backend)) $this->backend=new \PHK\Backend($this); return $this->backend; } //-------------- // Forward unknown method calls to the slow backend public function __call($method,$args) { $this->validate(); return \PHK\Tools\Util::callMethod($this->backend(),$method,$args); } //--------- public static function prolog($file,&$cmd,&$ret) { # Do we run in CLI mode ? if ($cli=(!\PHK\Tools\Util::envIsWeb())) { ini_set('display_errors',true); ini_set('memory_limit','1024M'); // Only in CLI mode } \PHK\Mgr::checkPhpVersion(); //-- Check PHP version - if unsupported, no return //----- // Mount the PHK file (or get the mount point if previously mounted) $mnt=\PHK\Mgr::mount($file); $phk=\PHK\Mgr::instance($mnt); //\PHK\Tools\Util::trace("Prolog mounted $file on $mnt");//TRACE //----- // Am I a main script ? // When there are symbolic links in the path, get_included_files() returns // 2 paths, the logical one first, and then the real one. $tmp=get_included_files(); $main=(($tmp[0]===$file) || (realpath($tmp[0]) === $file)); if (!$main) // Not main script { if (!is_null($script=$phk->option('lib_run_script'))) { require($phk->uri($script)); } if ($phk->option('auto_umount')) { \PHK\Mgr::umount($mnt); $ret=''; } else $ret=$mnt; return; } //----------------- // Main script - Dispatch if ($cli) { if (($_SERVER['argc']>1) && ($_SERVER['argv'][1]!='') && ($_SERVER['argv'][1]{0}=='@')) { $ret=$phk->builtinProlog($file); return; } // Not a command: call cli_run if (!is_null($run_path=$phk->option('cli_run_script'))) { $cmd="\$_phk_ret=require('".$phk->uri($run_path)."');"; } return; } else // HTTP mode { if (file_exists($file.'.webinfo')) // Slow path { \PHK\Tools\Util::runWebInfo($phk); } else { $cmd=$phk->webTunnel(); } } } //--- } // End of class //=========================================================================== } // End of class_exists //=========================================================================== } // End of namespace //=========================================================================== ?>