<?php
/** * Wave Framework <http://www.waveframework.com> * File Handler * * File Handler is used for returning a file from web server. It is loaded by Index Controller * when a request is made to a file that has an extension that has not already been served by * another Handler (such as Resource and Image handlers). File Handler is usually considered * for returning documents, videos and audio from the web server. It also allows to return a * file within a byte range, like in the case of file streams. * * @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/handler_file.htm * @since 1.5.0 * @version 3.6.4 */
// INITIALIZATION
// Stopping all requests that did not come from Index Gateway if(!isset($resourceAddress)){ header('HTTP/1.1 403 Forbidden'); die(); } // If access control header is set in configuration if(isset($config['access-control'])){ header('Access-Control-Allow-Origin: '.$config['access-control']); }
// If filename includes & symbol, then system assumes it should be dynamically generated $parameters=array_unique(explode('&',$resourceFile));
// Getting the downloadable file name $resourceFile=array_pop($parameters);
// The amount of non-filenames in the request $parameterCount=count($parameters); // Range of bytes to return // This allows user agent to request only part of the file if(isset($_SERVER['HTTP_RANGE'])){ $tmp=explode('=',$_SERVER['HTTP_RANGE']); $bytesData=explode('-',array_pop($tmp)); if(isset($bytesData) && is_numeric($bytesData[0]) && is_numeric($bytesData[1])){ $bytesFrom=$bytesData[0]; $bytesTo=$bytesData[1]; } }
// No cache flag $noCache=array_search('nocache',$parameters); if($noCache!==false){ // Unsetting the key for nocache parameter unset($parameters[$noCache]); } // Web root is the subfolder on public site $webRoot=str_replace('index.php','',$_SERVER['SCRIPT_NAME']);
// Checking if the file might be loaded from overrides folder if(preg_match('/^'.str_replace('/','\/',$webRoot).'resources\//',$_SERVER['REQUEST_URI'])){ // Solving possible overrides folder $overridesFolder=str_replace($webRoot.'resources'.DIRECTORY_SEPARATOR,$webRoot.'overrides'.DIRECTORY_SEPARATOR.'resources'.DIRECTORY_SEPARATOR,$resourceFolder); if(file_exists($overridesFolder.$resourceFile)){ $resourceFolder=$overridesFolder; } }
// Default cache timeout of one month, unless timeout is set if(!isset($config['resource-cache-timeout'])){ $config['resource-cache-timeout']=31536000; // A year }
// CHECK FOR PARAMETER SUPPORT
// If more than one parameter is set, it returns 404 // 404 is also returned if file does not actually exist if($parameterCount>1 || ($parameterCount==1 && !$noCache) || !file_exists($resourceFolder.$resourceFile)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('category'=>'file','response-code'=>'404')); // Writing log entry $logger->writeLog(); } // Returning 404 header header('HTTP/1.1 404 Not Found'); die(); }
// Last-modified date $lastModified=filemtime($resourceFolder.$resourceFile);
// NOT MODIFIED CHECK
// Checking if file has been modified or not if(!$noCache){ // If the request timestamp is exactly the same, then we let the browser know of this if((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])==$lastModified) || (isset($_SERVER['HTTP_IF_RANGE']) && strtotime($_SERVER['HTTP_IF_RANGE'])==$lastModified)){ // Adding log entry if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>true,'category'=>'image','response-code'=>'304')); // Writing log entry $logger->writeLog(); } // Cache headers (Last modified is never sent with 304 header) header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); // Returning 304 header header('HTTP/1.1 304 Not Modified'); die(); } }
// DETECTING MIME TYPE
// Currently assumed MIME type $mimeType=''; // Finding the proper MIME type if(extension_loaded('fileinfo')){ // This opens MIME type 'magic' resource for use if($fileInfo=finfo_open(FILEINFO_MIME_TYPE)){ // Finding MIME type with magic resource $mimeType=finfo_file($fileInfo,$resourceFolder.$resourceFile); // Resourse is not needed further, so it is closed finfo_close($fileInfo); } } else { // Since Fileinfo was not available, we use extension-based detection as fallback if(isset($resourceExtension)){ switch($resourceExtension){ case 'ico': $mimeType='image/vnd.microsoft.icon;'; break; case 'zip': $mimeType='application/zip'; break; case 'pdf': $mimeType='application/pdf'; break; case 'mp3': $mimeType='audio/mpeg'; break; case 'gif': $mimeType='image/gif'; break; case 'tif': $mimeType='image/tiff'; break; } } } // HEADERS
// Assigning MIME type if it was found if($mimeType && $mimeType!=''){ // Detected mime type is set as content-type header header('Content-Type: '.$mimeType.';'); } else { // Octet stream is a general-use unknown resource, and browsers will often attempt to 'download' such a file header('Content-Type: application/octet-stream;'); header('Content-Disposition: attachment; filename='.$resourceFile); } // If cache is used, then proper headers will be sent if($noCache){ // User agent is told to cache these results for set duration header('Cache-Control: no-cache,no-store'); header('Expires: '.gmdate('D, d M Y H:i:s',$_SERVER['REQUEST_TIME']).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); } else { // User agent is told to cache these results for set duration header('Cache-Control: public,max-age='.$config['resource-cache-timeout']); header('Expires: '.gmdate('D, d M Y H:i:s',($_SERVER['REQUEST_TIME']+$config['resource-cache-timeout'])).' GMT'); header('Last-Modified: '.gmdate('D, d M Y H:i:s',$lastModified).' GMT'); }
// Robots header if(isset($config['file-robots'])){ // If file-specific robots setting is defined header('Robots-Tag: '.$config['file-robots'],true); } elseif(isset($config['robots'])){ // This sets general robots setting, if it is defined in configuration file header('Robots-Tag: '.$config['robots'],true); } else { // If robots setting is not configured, system tells user agent not to cache the file header('Robots-Tag: noindex,nocache,nofollow,noarchive,noimageindex,nosnippet',true); } // OUTPUT
// If user agent only requested part of the file to be returned if(isset($bytesFrom,$bytesTo)){ // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); if($bytesTo<=$contentLength){ // Required for range response header('HTTP/1.1 206 Partial Content'); header('Content-Range: bytes '.$bytesFrom.'-'.$bytesTo.'/'.$contentLength); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.($bytesTo-$bytesFrom)); // Returning part of the file $fileHandle=fopen($resourceFolder.$resourceFile,'r'); fseek($fileHandle,$bytesFrom); // Returning the data to user agent echo fread($fileHandle,($bytesTo-$bytesFrom)); } else { header('HTTP/1.1 416 Requested Range Not Satisfiable'); } } else { // Getting current output length $contentLength=filesize($resourceFolder.$resourceFile); // Content length is defined that can speed up website requests, letting user agent to determine file size header('Content-Length: '.$contentLength); // Returning the file to user agent readfile($resourceFolder.$resourceFile); } // WRITING TO LOG
// If Logger is defined then request is logged and can be used for performance review later if(isset($logger)){ // Assigning custom log data to logger $logger->setCustomLogData(array('cache-used'=>false,'category'=>'file','content-length-used'=>$contentLength)); // Writing log entry $logger->writeLog(); }
?>
|