<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Routing\Filter;
use Cake\Core\Plugin;
use Cake\Event\Event;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\Routing\DispatcherFilter;
use Cake\Utility\Inflector;
/**
* Filters a request and tests whether it is a file in the webroot folder or not and
* serves the file to the client if appropriate.
*/
class AssetFilter extends DispatcherFilter
{
/**
* Default priority for all methods in this filter
* This filter should run before the request gets parsed by router
*
* @var int
*/
protected $_priority = 9;
/**
* The amount of time to cache the asset.
*
* @var string
*/
protected $_cacheTime = '+1 day';
/**
*
* Constructor.
*
* @param array $config Array of config.
*/
public function __construct($config = [])
{
if (!empty($config['cacheTime'])) {
$this->_cacheTime = $config['cacheTime'];
}
parent::__construct($config);
}
/**
* Checks if a requested asset exists and sends it to the browser
*
* @param \Cake\Event\Event $event Event containing the request and response object
* @return \Cake\Http\Response|null If the client is requesting a recognized asset, null otherwise
* @throws \Cake\Http\Exception\NotFoundException When asset not found
*/
public function beforeDispatch(Event $event)
{
/** @var \Cake\Http\ServerRequest $request */
$request = $event->getData('request');
$url = urldecode($request->getUri()->getPath());
if (strpos($url, '..') !== false || strpos($url, '.') === false) {
return null;
}
$assetFile = $this->_getAssetFile($url);
if ($assetFile === null || !file_exists($assetFile)) {
return null;
}
/** @var \Cake\Http\Response $response */
$response = $event->getData('response');
$event->stopPropagation();
$response = $response->withModified(filemtime($assetFile));
if ($response->checkNotModified($request)) {
return $response;
}
$pathSegments = explode('.', $url);
$ext = array_pop($pathSegments);
return $this->_deliverAsset($request, $response, $assetFile, $ext);
}
/**
* Builds asset file path based off url
*
* @param string $url Asset URL
* @return string Absolute path for asset file
*/
protected function _getAssetFile($url)
{
$parts = explode('/', ltrim($url, '/'));
$pluginPart = [];
for ($i = 0; $i < 2; $i++) {
if (!isset($parts[$i])) {
break;
}
$pluginPart[] = Inflector::camelize($parts[$i]);
$plugin = implode('/', $pluginPart);
if ($plugin && Plugin::isLoaded($plugin)) {
$parts = array_slice($parts, $i + 1);
$fileFragment = implode(DIRECTORY_SEPARATOR, $parts);
$pluginWebroot = Plugin::path($plugin) . 'webroot' . DIRECTORY_SEPARATOR;
return $pluginWebroot . $fileFragment;
}
}
}
/**
* Sends an asset file to the client
*
* @param \Cake\Http\ServerRequest $request The request object to use.
* @param \Cake\Http\Response $response The response object to use.
* @param string $assetFile Path to the asset file in the file system
* @param string $ext The extension of the file to determine its mime type
* @return \Cake\Http\Response The updated response.
*/
protected function _deliverAsset(ServerRequest $request, Response $response, $assetFile, $ext)
{
$compressionEnabled = $response->compress();
if ($response->getType() === $ext) {
$contentType = 'application/octet-stream';
$agent = $request->getEnv('HTTP_USER_AGENT');
if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
$contentType = 'application/octetstream';
}
$response = $response->withType($contentType);
}
if (!$compressionEnabled) {
$response = $response->withHeader('Content-Length', filesize($assetFile));
}
$response = $response->withCache(filemtime($assetFile), $this->_cacheTime)
->withFile($assetFile);
return $response;
}
}