PHP Classes

File: index.php

Recommend this page to a friend!
  Classes of Kristo Vaher   Wave Framework   index.php   Download  
File: index.php
Role: Application script
Content type: text/plain
Description: Index Gateway
Class: Wave Framework
MVC framework for building Web sites and APIs
Author: By
Last change: Update of index.php
Date: 3 months ago
Size: 19,462 bytes
 

Contents

Class file image Download
<?php /** * Wave Framework <http://github.com/kristovaher/Wave-Framework> * Index Gateway * * Index Gateway is an index/bootstrap file of Wave Framework that will serve almost every HTTP * request made to the system built on Wave Framework. It analyzes the HTTP request, loads Logger * and configuration as well as HTTP request Limiter, overwrites error handler of PHP and then * executes the command through one of the request handlers that are stored in * /engine/handler.[handler-type].php files. * * @package Index Gateway * @author Kristo Vaher <kristo@waher.net> * @copyright Copyright (c) 2012, Kristo Vaher * @license GNU Lesser General Public License Version 3 * @tutorial /doc/pages/gateway.htm * @since 1.0.0 * @version 3.7.0 */ // SOLVING THE HTTP REQUEST // For performance logging $microTime=microtime(true); // Custom error-handling and reporting is used // Make sure to check for errors with /tools/debugger.php script (especially when you encounter a critical error page) error_reporting(0); // Getting resource without GET string $tmp=explode('?',$_SERVER['REQUEST_URI']); $resourceAddress=array_shift($tmp); // Stopping all direct requests to Index Gateway if($resourceAddress==$_SERVER['SCRIPT_NAME']){ header('HTTP/1.1 403 Forbidden'); die(); } // Currently known location of the file in filesystem // Double replacement occurs since some environments give document root with the slash in the end, some don't (like Windows) $resourceRequest=str_replace(DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR,DIRECTORY_SEPARATOR,$_SERVER['DOCUMENT_ROOT'].$resourceAddress); // If requested URL does not point to a directory, then request is possibly made to a file if(!is_dir($resourceRequest)){ // Getting directory, filename and extension information about current resource address $resourceInfo=pathinfo($resourceRequest); // Solving the folder that user agent is loading resource from $resourceFolder=$resourceInfo['dirname'].DIRECTORY_SEPARATOR; // Assigning file information $resourceFile=$resourceInfo['basename']; // If extension was detected then this too is used if(isset($resourceInfo['extension'])){ $resourceExtension=$resourceInfo['extension']; } } // LOADING CONFIGURATION // Defining root directory, this is required by handlers in /engine/ subfolder define('__ROOT__',__DIR__.DIRECTORY_SEPARATOR); //Configuration is stored in this array, it has to be defined even if no configuration is loaded $config=array(); // Including the configuration if(!file_exists(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'config.tmp') || filemtime(__ROOT__.'config.ini')>filemtime(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'config.tmp')){ // Configuration is parsed from INI file in the root of the system $config=parse_ini_file(__ROOT__.'config.ini',false,INI_SCANNER_RAW); // Loading version numbers $versionsRaw=explode("\n",str_replace("\r",'',file_get_contents(__ROOT__.'.version'))); foreach($versionsRaw as $ver){ // Versions are separated by colon in the version file $tmp=explode(':',$ver); $config['version-'.$tmp[0]]=$tmp[1]; } // List of logger IP's if(isset($config['logger-ip'])){ $config['logger-ip']=explode(',',$config['logger-ip']); } // List of languages if(isset($config['languages'])){ $config['languages']=explode(',',$config['languages']); } // Internal logging flags if(isset($config['internal-logging'])){ $config['internal-logging']=explode(',',$config['internal-logging']); } // API logging settings if(isset($config['api-logging'])){ $config['api-logging']=explode(',',$config['api-logging']); } // API versions if(isset($config['api-versions'])){ // This also makes sure that the most recent version number exists in API versions list $config['api-versions']=explode(',',$config['api-versions']); if(!in_array($config['version-api'],$config['api-versions'])){ $config['api-versions'][]=$config['version-api']; } } else { $config['api-versions']=array($config['version-api']); } // File extensions and defaults if(isset($config['image-extensions'])){ $config['image-extensions']=explode(',',$config['image-extensions']); } else { $config['image-extensions']=array('jpeg','jpg','png'); } if(isset($config['resource-extensions'])){ $config['resource-extensions']=explode(',',$config['resource-extensions']); } else { $config['resource-extensions']=array('css','js','txt','csv','xml','html','htm','rss','vcard','appcache'); } if(isset($config['file-extensions'])){ $config['file-extensions']=explode(',',$config['file-extensions']); } else { $config['file-extensions']=array('pdf','doc','docx','xls','xlsx','ppt','pptx','zip','rar'); } if(isset($config['forbidden-extensions'])){ $config['forbidden-extensions']=explode(',',$config['forbidden-extensions']); } else { $config['forbidden-extensions']=array('tmp','log','ht','htaccess','pem','crt','db','sql','version','conf','ini','empty'); } // Required timezone setting if(!isset($config['timezone'])){ // Setting GMT as the default timezone $config['timezone']='Europe/London'; } // Trusted proxies and IP address if(isset($config['trusted-proxies'])){ $config['trusted-proxies']=explode(',',$config['trusted-proxies']); } else { $config['trusted-proxies']=array('*'); } // Trusted proxies and IP address if(isset($config['session-fingerprint'])){ $config['session-fingerprint']=explode(',',$config['session-fingerprint']); } // Cache of parsed INI file is stored for later use if(!file_put_contents(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'config.tmp',serialize($config))){ header('HTTP/1.1 500 Internal Server Error'); echo '<h1>HTTP/1.1 500 Internal Server Error</h1>'; echo '<p>Cannot write cache in filesystem, please make sure filesystem folders are writable.</p>'; } } else { // Since INI file has not been changed, configuration is loaded from cache $config=unserialize(file_get_contents(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'cache'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR.'config.tmp')); } // Setting the timezone date_default_timezone_set($config['timezone']); // IP may be forwarded (such as when website is used through a proxy), this can check for such an occasion if(isset($_SERVER['HTTP_CLIENT_IP']) && (in_array('*',$config['trusted-proxies']) || in_array($_SERVER['REMOTE_ADDR'],$config['trusted-proxies']))){ $tmp=explode(',',$_SERVER['HTTP_CLIENT_IP']); define('__IP__',trim(array_pop($tmp))); } elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && (in_array('*',$config['trusted-proxies']) || in_array($_SERVER['REMOTE_ADDR'],$config['trusted-proxies']))){ $tmp=explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']); define('__IP__',trim(array_pop($tmp))); } else { define('__IP__',$_SERVER['REMOTE_ADDR']); } // LOADING LOGGER // Logger file is used for performance logging for later review // Configuration file can set what type of logging is used if(isset($config['logger']) && $config['logger']!=false && (!isset($config['logger-ip']) || in_array('*',$config['logger-ip']) || in_array(__IP__,$config['logger-ip'])) && ($config['logger']=='*' || preg_match($config['logger'],$_SERVER['REQUEST_URI']))){ require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-logger.php'); $logger=new WWW_Logger(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'logs'.DIRECTORY_SEPARATOR,$microTime); } // LOADING HTTP REQUEST LIMITER // If limiter is configured to be used if(isset($config['limiter']) && $config['limiter']){ // Limiter is used to block requests under specific conditions, like DOS attacks or when server load is too high require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'class.www-limiter.php'); $limiter=new WWW_Limiter(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'limiter'.DIRECTORY_SEPARATOR); // Assigning logger to Limiter // Logger is used to output log data in case Limiter stops the script pre-maturely if(isset($logger)){ $limiter->logger=$logger; } // Load limiter blocks access if server load is detected to be too high at the moment of request if(isset($config['limiter-load']) && $config['limiter-load']){ $limiter->limitServerLoad($config['limiter-load']); } // Load limiter allows access for certain IP's or blocks access to specific blacklist of IP's if(isset($config['limiter-whitelist']) && $config['limiter-whitelist']){ $limiter->limitWhitelisted($config['limiter-whitelist']); } elseif(isset($config['limiter-blacklist']) && $config['limiter-blacklist']){ $limiter->limitBlacklisted($config['limiter-blacklist']); } // If HTTPS limiter is used, the ststem returns a 401 error if the user agent attempts to access the site without HTTPS if(isset($config['limiter-https']) && $config['limiter-https']){ $limiter->limitNonSecureRequests(); // By default the user agent is redirected to HTTPS address of the same request } // If HTTP authentication is turned on, the system checks for credentials and returns 401 if failed if(isset($config['limiter-authentication']) && $config['limiter-authentication']){ $limiter->limitUnauthorized($config['http-authentication-username'],$config['http-authentication-password'],((isset($config['http-authentication-ip']))?$config['http-authentication-ip']:'*')); } // Request limiter keeps track of how many requests per minute are allowed on IP. // If limit is exceeded, then IP is blocked for an hour. if(isset($config['limiter-request']) && $config['limiter-request']){ $limiter->limitRequestCount($config['limiter-request']); } // Referrer limiter checks if the Referrer URL is allowed or not if(isset($config['limiter-referrer']) && $config['limiter-referrer']){ $limiter->limitReferrer($config['limiter-referrer']); } } // ERROR AND EXIT HANDLING // This error handler replaces default PHP error handler and is tied to Exception class function WWW_exitHandler($type=false,$message=false,$file=false,$line=false){ // if this is called through error handler if($message){ $errorCheck=array(); $errorCheck['type']=$type; $errorCheck['message']=$message; $errorCheck['file']=$file; $errorCheck['line']=$line; } else { // Getting the last thrown error, if any $errorCheck=error_get_last(); } // If there is an error and it is not a deprecated Line 0 error (which sporadically is thrown in PHP 5.3.4) if($errorCheck && ($errorCheck['line']!=0 || $errorCheck['type']<E_DEPRECATED)){ // Using the global to access configuration settings beyond the scope of this method global $config; // Detecting if error is fatal - thus if error message should be shown to the user $fatalError=false; if(in_array($errorCheck['type'],array(E_ERROR,E_USER_ERROR,E_CORE_ERROR,E_PARSE))){ $fatalError=true; } // Testing the configuration option for error-reporting if(!isset($config['errors-reporting']) || $config['errors-reporting']=='full' || ($fatalerror && $config['errors-reporting']=='critical')){ // Error report will be stored in this array $errorReport=array(); // Setting GMT as the error-reporting timezone $errorReport[]=date('d.m.Y H:i:s'); // Input data to error report $error=array(); if(!empty($_GET)){ $error['get']=$_GET; } if(!empty($_POST)){ $error['post']=$_POST; } if(!empty($_FILES)){ $error['files']=$_FILES; } if(!empty($_COOKIE)){ $error['cookies']=$_COOKIE; } if(!empty($_SERVER)){ $error['server']=$_SERVER; } // Add to error array $errorReport[]=$error; //Adding backtrace if(isset($config['errors-trace']) && $config['errors-trace']==1){ $errorReport[]=debug_backtrace(); } // Writing current error and file to the array as well $error=array(); $error['url']=$_SERVER['REQUEST_URI']; $error['type']=$errorCheck['type']; $error['file']=$errorCheck['file']; $error['line']=$errorCheck['line']; $error['message']=$errorCheck['message']; $errorReport[]=$error; // This is the signature used for storing developer sessions $signatureFolder=md5($_SERVER['REMOTE_ADDR'].' '.$_SERVER['HTTP_USER_AGENT']); // If error folder does not yet exist if(!is_dir(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'errors'.DIRECTORY_SEPARATOR.$signatureFolder.DIRECTORY_SEPARATOR)){ if(!mkdir(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'errors'.DIRECTORY_SEPARATOR.$signatureFolder.DIRECTORY_SEPARATOR,0755)){ trigger_error('Cannot create error folder',E_USER_ERROR); } file_put_contents(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'errors'.DIRECTORY_SEPARATOR.$signatureFolder.DIRECTORY_SEPARATOR.'signature.tmp',$_SERVER['REMOTE_ADDR'].' '.$_SERVER['HTTP_USER_AGENT']); } // Logging the error, the error filename is calculated from current error message (this makes sure there are no duplicates, if the error message is the same). file_put_contents(__ROOT__.'filesystem'.DIRECTORY_SEPARATOR.'errors'.DIRECTORY_SEPARATOR.$signatureFolder.DIRECTORY_SEPARATOR.md5($error['file'].$error['message']).'.tmp',json_encode($errorReport)."\n",FILE_APPEND); // As long as the error level is set to display errors of this type if($fatalError){ // Cleaning output buffer, if it exists if(ob_get_level()>=1){ ob_end_clean(); } // There was an error in code // System returns 500 header even as a server error (possible bug in code) header('HTTP/1.1 500 Internal Server Error'); // Regular users will be shown a friendly error message if(isset($config['errors-verbose']) && $config['errors-verbose']==1){ echo '<div style="font:18px Tahoma; text-align:center;padding:100px 50px 10px 50px;">CRITICAL ERROR ENCOUNTERED</div>'; echo '<div style="font:12px Tahoma;width:500px;margin:auto;padding:5px 50px 5px 50px;"><b>TYPE</b>: '.htmlspecialchars($errorCheck['type']).'</div>'; echo '<div style="font:12px Tahoma;width:500px;margin:auto;padding:5px 50px 5px 50px;"><b>FILE</b>: '.htmlspecialchars($errorCheck['file']).'</div>'; echo '<div style="font:12px Tahoma;width:500px;margin:auto;padding:5px 50px 5px 50px;"><b>LINE</b>: '.htmlspecialchars($errorCheck['line']).'</div>'; echo '<div style="font:12px Tahoma;width:500px;margin:auto;padding:5px 50px 5px 50px;"><b>MESSAGE</b>: '.htmlspecialchars($errorCheck['message']).'</div>'; if(isset($config['errors-trace']) && $config['errors-trace']==1){ echo '<div style="font:14px Tahoma; text-align:center;padding:10px 50px 100px 50px;">FULL STACK TRACE AVAILABLE FROM DEBUGGER SCRIPT</div>'; } else { echo '<div style="font:14px Tahoma; text-align:center;padding:10px 50px 100px 50px;">FULL STACK TRACE IS NOT LOGGED BY DEBUGGER</div>'; } } else { echo '<div style="font:18px Tahoma; text-align:center;padding:100px 50px 10px 50px;">WE ARE CURRENTLY EXPERIENCING A PROBLEM WITH YOUR REQUEST</div>'; echo '<div style="font:14px Tahoma; text-align:center;padding:10px 50px 100px 50px;">ERROR HAS BEEN LOGGED FOR FURTHER INVESTIGATION</div>'; } // Closing the entire request die(); } } } } // Setting the error handler set_error_handler('WWW_exitHandler',E_ALL); register_shutdown_function('WWW_exitHandler'); // LOADING HANDLERS // Index Gateway works differently based on what file is being requested // Handlers for all different modes are stored under /engine/ subfolder // request has a file extension, then system will attempt to use another handler if(isset($resourceExtension)){ // Handler is detected based on requested file extension if(in_array($resourceExtension,$config['image-extensions'])){ // Image Handler allows for things such as dynamic image loading require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.image.php'); } elseif(in_array($resourceExtension,$config['resource-extensions'])){ // Text-based resources are handled by Resource Handler, except for two special cases (robots.txt and sitemap.xml) if($resourceFile=='sitemap.xml'){ // Sitemap is dynamically generated from sitemap files in /resource/ subfolder require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.sitemap.php'); } elseif($resourceFile=='robots.txt'){ // Robots file is dynamically generated based on 'robots' configuration in config.php file require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.robots.php'); } elseif($resourceExtension=='appcache'){ // Appcache settings can be dynamically generated require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.appcache.php'); } else { // In every other case the system loads text based resources with additional options, such as compressions and minifying, with Resource Handler require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.resource.php'); } } elseif(in_array($resourceExtension,$config['file-extensions'])){ // File Handler is loaded for every other file request case require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.file.php'); } elseif(in_array($resourceExtension,$config['forbidden-extensions'])){ // These file extensions are not allowed, thus 403 error is returned // Log category is 'file' due to it being a file with an extension if(isset($logger)){ $logger->setCustomLogData(array('response-code'=>403,'category'=>'file')); $logger->writeLog(); } // Returning 403 header header('HTTP/1.1 403 Forbidden'); die(); } elseif($resourceExtension=='api'){ // Replacing the extension in the request to find handler filename $apiHandler=str_replace('.api','',$resourceFile); // Replacing all potentially sensitive characters from API handler name $apiHandler=preg_replace('/[^0-9a-z\-\_]/i','',$apiHandler); // If the file exists then system loads the new API, otherwise 404 is returned if(file_exists(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.api-'.$apiHandler.'.php')){ // Custom API files need to be placed in engine subfolder // To see how the default API is built, take a look at handler.api.php require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.api-'.$apiHandler.'.php'); } else { // This allows API filename to define what type of data should be returned if($apiHandler=='www'){ if(!isset($_GET['www-return-type'])){ $_GET['www-return-type']='php'; } } elseif($apiHandler!='json' && $apiHandler!='www'){ $_GET['www-return-type']=$apiHandler; } require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.api.php'); } } else { // Every other extension is handled by Data Handler, which loads URL and View controllers for website views require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.data.php'); } } else { // Every other request is handled by Data Handler, which loads URL and View controllers for website views require(__ROOT__.'engine'.DIRECTORY_SEPARATOR.'handler.data.php'); } ?>