1<?php 2/** 3 * CakePHP(tm) Tests <https://book.cakephp.org/view/1196/Testing> 4 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 5 * 6 * Licensed under The MIT License 7 * For full copyright and license information, please see the LICENSE.txt 8 * Redistributions of files must retain the above copyright notice. 9 * 10 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) 11 * @link https://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests 12 * @package Cake.Test.Case.Routing.Filter 13 * @since CakePHP(tm) v 2.2 14 * @license https://opensource.org/licenses/mit-license.php MIT License 15 */ 16 17App::uses('AssetDispatcher', 'Routing/Filter'); 18App::uses('CakeEvent', 'Event'); 19App::uses('CakeResponse', 'Network'); 20 21/** 22 * AssetDispatcherTest 23 * 24 * @package Cake.Test.Case.Routing.Filter 25 */ 26class AssetDispatcherTest extends CakeTestCase { 27 28/** 29 * tearDown method 30 * 31 * @return void 32 */ 33 public function tearDown() { 34 parent::tearDown(); 35 Configure::write('Dispatcher.filters', array()); 36 } 37 38/** 39 * test that asset filters work for theme and plugin assets 40 * 41 * @return void 42 * @triggers DispatcherTest $this, compact('request', 'response') 43 * @triggers DispatcherTest $this, compact('request', 'response') 44 * @triggers DispatcherTest $this, compact('request', 'response') 45 * @triggers DispatcherTest $this, compact('request', 'response') 46 * @triggers DispatcherTest $this, compact('request', 'response') 47 * @triggers DispatcherTest $this, compact('request', 'response') 48 */ 49 public function testAssetFilterForThemeAndPlugins() { 50 $filter = new AssetDispatcher(); 51 $response = $this->getMock('CakeResponse', array('_sendHeader')); 52 Configure::write('Asset.filter', array( 53 'js' => '', 54 'css' => '' 55 )); 56 App::build(array( 57 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 58 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 59 ), App::RESET); 60 61 $request = new CakeRequest('theme/test_theme/ccss/cake.generic.css'); 62 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 63 $this->assertSame($response, $filter->beforeDispatch($event)); 64 $this->assertTrue($event->isStopped()); 65 66 $request = new CakeRequest('theme/test_theme/cjs/debug_kit.js'); 67 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 68 $this->assertSame($response, $filter->beforeDispatch($event)); 69 $this->assertTrue($event->isStopped()); 70 71 $request = new CakeRequest('test_plugin/ccss/cake.generic.css'); 72 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 73 $this->assertSame($response, $filter->beforeDispatch($event)); 74 $this->assertTrue($event->isStopped()); 75 76 $request = new CakeRequest('test_plugin/cjs/debug_kit.js'); 77 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 78 $this->assertSame($response, $filter->beforeDispatch($event)); 79 $this->assertTrue($event->isStopped()); 80 81 $request = new CakeRequest('css/ccss/debug_kit.css'); 82 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 83 $this->assertNull($filter->beforeDispatch($event)); 84 $this->assertFalse($event->isStopped()); 85 86 $request = new CakeRequest('js/cjs/debug_kit.js'); 87 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 88 $this->assertNull($filter->beforeDispatch($event)); 89 $this->assertFalse($event->isStopped()); 90 } 91 92/** 93 * AssetDispatcher should not 404 extensions that could be handled 94 * by Routing. 95 * 96 * @return void 97 * @triggers DispatcherTest $this, compact('request', 'response') 98 */ 99 public function testNoHandleRoutedExtension() { 100 $filter = new AssetDispatcher(); 101 $response = $this->getMock('CakeResponse', array('_sendHeader')); 102 Configure::write('Asset.filter', array( 103 'js' => '', 104 'css' => '' 105 )); 106 App::build(array( 107 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 108 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 109 ), App::RESET); 110 Router::parseExtensions('json'); 111 Router::connect('/test_plugin/api/v1/:action', array('controller' => 'api')); 112 CakePlugin::load('TestPlugin'); 113 114 $request = new CakeRequest('test_plugin/api/v1/forwarding.json'); 115 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 116 $this->assertNull($filter->beforeDispatch($event)); 117 $this->assertFalse($event->isStopped(), 'Events for routed extensions should not be stopped'); 118 } 119 120/** 121 * Tests that $response->checkNotModified() is called and bypasses 122 * file dispatching 123 * 124 * @return void 125 * @triggers DispatcherTest $this, compact('request', 'response') 126 * @triggers DispatcherTest $this, compact('request', 'response') 127 */ 128 public function testNotModified() { 129 $filter = new AssetDispatcher(); 130 Configure::write('Asset.filter', array( 131 'js' => '', 132 'css' => '' 133 )); 134 App::build(array( 135 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 136 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 137 )); 138 $time = filemtime(App::themePath('TestTheme') . 'webroot' . DS . 'img' . DS . 'cake.power.gif'); 139 $time = new DateTime('@' . $time); 140 141 $response = $this->getMock('CakeResponse', array('send', 'checkNotModified')); 142 $request = new CakeRequest('theme/test_theme/img/cake.power.gif'); 143 144 $response->expects($this->once())->method('checkNotModified') 145 ->with($request) 146 ->will($this->returnValue(true)); 147 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 148 149 ob_start(); 150 $this->assertSame($response, $filter->beforeDispatch($event)); 151 ob_end_clean(); 152 $this->assertEquals(200, $response->statusCode()); 153 $this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->modified()); 154 155 $response = $this->getMock('CakeResponse', array('_sendHeader', 'checkNotModified')); 156 $request = new CakeRequest('theme/test_theme/img/cake.power.gif'); 157 $response->expects($this->once())->method('checkNotModified') 158 ->with($request) 159 ->will($this->returnValue(true)); 160 $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response')); 161 $this->assertSame($response, $filter->beforeDispatch($event)); 162 $this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->modified()); 163 } 164 165/** 166 * Test that no exceptions are thrown for //index.php type URLs. 167 * 168 * @return void 169 * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') 170 */ 171 public function test404OnDoubleSlash() { 172 $filter = new AssetDispatcher(); 173 174 $response = $this->getMock('CakeResponse', array('_sendHeader')); 175 $request = new CakeRequest('//index.php'); 176 $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); 177 178 $this->assertNull($filter->beforeDispatch($event)); 179 $this->assertFalse($event->isStopped()); 180 } 181 182/** 183 * Test that attempts to traverse directories are prevented. 184 * 185 * @return void 186 * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') 187 */ 188 public function test404OnDoubleDot() { 189 App::build(array( 190 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 191 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 192 ), App::RESET); 193 $response = $this->getMock('CakeResponse', array('_sendHeader')); 194 $request = new CakeRequest('theme/test_theme/../../../../../../VERSION.txt'); 195 $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); 196 $filter = new AssetDispatcher(); 197 $this->assertNull($filter->beforeDispatch($event)); 198 $this->assertFalse($event->isStopped()); 199 } 200 201/** 202 * Test that attempts to traverse directories with urlencoded paths fail. 203 * 204 * @return void 205 * @triggers Dispatcher.beforeRequest $this, compact('request', 'response') 206 */ 207 public function test404OnDoubleDotEncoded() { 208 App::build(array( 209 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), 210 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 211 ), App::RESET); 212 213 $response = $this->getMock('CakeResponse', array('_sendHeader', 'send')); 214 $request = new CakeRequest('theme/test_theme/%2e./%2e./%2e./%2e./%2e./%2e./VERSION.txt'); 215 $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); 216 217 $response->expects($this->never())->method('send'); 218 219 $filter = new AssetDispatcher(); 220 $this->assertNull($filter->beforeDispatch($event)); 221 $this->assertFalse($event->isStopped()); 222 } 223 224/** 225 * Test asset content length is unset 226 * 227 * If content length is unset, then the webserver can figure it out. 228 * 229 * @outputBuffering enabled 230 * @return void 231 */ 232 public function testAssetContentLength() { 233 Router::reload(); 234 Configure::write('Dispatcher.filters', array('AssetDispatcher')); 235 App::build(array( 236 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS) 237 )); 238 239 $url = 'theme/test_theme/css/test_asset.css'; 240 $file = 'View/Themed/TestTheme/webroot/css/test_asset.css'; 241 242 $request = new CakeRequest($url); 243 $response = $this->getMock('CakeResponse', array('_sendHeader', 'send')); 244 $event = new CakeEvent('Dispatcher.beforeRequest', $this, compact('request', 'response')); 245 246 $filter = new AssetDispatcher(); 247 $filter->beforeDispatch($event); 248 $result = ob_get_clean(); 249 250 $path = CAKE . 'Test' . DS . 'test_app' . DS . str_replace('/', DS, $file); 251 $file = file_get_contents($path); 252 $this->assertEquals($file, $result); 253 254 $headers = $response->header(); 255 $this->assertFalse($headers['Content-Length']); 256 } 257 258} 259