@@ -255,4 +255,139 @@ public static function renderDataProvider(): array
255255 ],
256256 ];
257257 }
258+
259+ public function testAbsolutePathStillRendersByDefault (): void
260+ {
261+ $ file = $ this ->makeOutsideTemplate ('outside ' );
262+
263+ try {
264+ $ this ->expectOutputString ('outside ' );
265+ $ this ->view ->render ($ file );
266+ $ this ->assertTrue ($ this ->view ->exists ($ file ));
267+ } finally {
268+ $ this ->removeDir (dirname ($ file ));
269+ }
270+ }
271+
272+ public function testRestrictToPathIsOffByDefault (): void
273+ {
274+ $ this ->assertFalse ((new View ())->restrictToPath );
275+ }
276+
277+ public function testRestrictToPathRendersViewInsidePath (): void
278+ {
279+ $ this ->view ->restrictToPath = true ;
280+
281+ $ this ->expectOutputString ('Hello, Bob! ' );
282+ $ this ->view ->render ('hello ' , ['name ' => 'Bob ' ]);
283+ $ this ->assertTrue ($ this ->view ->exists ('hello ' ));
284+ $ this ->assertTrue ($ this ->view ->exists ('layouts/layout ' ));
285+ }
286+
287+ public function testRestrictToPathAllowsAbsolutePathInsidePath (): void
288+ {
289+ $ this ->view ->restrictToPath = true ;
290+ $ file = (string ) realpath (__DIR__ . '/views/hello.php ' );
291+
292+ $ this ->expectOutputString ('Hello, Bob! ' );
293+ $ this ->view ->render ($ file , ['name ' => 'Bob ' ]);
294+ }
295+
296+ public function testRestrictToPathRejectsRelativePathOutsidePath (): void
297+ {
298+ $ this ->view ->restrictToPath = true ;
299+ $ dir = __DIR__ . DIRECTORY_SEPARATOR . 'restrict- ' . uniqid ();
300+ mkdir ($ dir );
301+ file_put_contents ($ dir . DIRECTORY_SEPARATOR . 'note.php ' , 'outside ' );
302+ $ name = '.. ' . DIRECTORY_SEPARATOR . basename ($ dir ) . DIRECTORY_SEPARATOR . 'note ' ;
303+
304+ try {
305+ $ this ->assertFalse ($ this ->view ->exists ($ name ));
306+ $ this ->expectException (Exception::class);
307+ $ this ->expectExceptionMessage ('Template file is outside the views path. ' );
308+ $ this ->view ->render ($ name );
309+ } finally {
310+ $ this ->removeDir ($ dir );
311+ }
312+ }
313+
314+ public function testRestrictToPathRejectsAbsolutePathOutsidePath (): void
315+ {
316+ $ this ->view ->restrictToPath = true ;
317+ $ file = $ this ->makeOutsideTemplate ('outside ' );
318+
319+ try {
320+ $ this ->assertFalse ($ this ->view ->exists ($ file ));
321+ $ this ->expectException (Exception::class);
322+ $ this ->expectExceptionMessage ('Template file is outside the views path. ' );
323+ $ this ->view ->render ($ file );
324+ } finally {
325+ $ this ->removeDir (dirname ($ file ));
326+ }
327+ }
328+
329+ public function testRestrictToPathRejectsSymlinkOutsidePath (): void
330+ {
331+ $ root = sys_get_temp_dir () . DIRECTORY_SEPARATOR . 'flight-view- ' . uniqid ();
332+ mkdir ($ root . DIRECTORY_SEPARATOR . 'views ' , 0777 , true );
333+ mkdir ($ root . DIRECTORY_SEPARATOR . 'outside ' );
334+ $ target = $ root . DIRECTORY_SEPARATOR . 'outside ' . DIRECTORY_SEPARATOR . 'note.php ' ;
335+ file_put_contents ($ target , 'outside ' );
336+
337+ if (!@symlink ($ target , $ root . DIRECTORY_SEPARATOR . 'views ' . DIRECTORY_SEPARATOR . 'alias.php ' )) {
338+ $ this ->removeDir ($ root );
339+ $ this ->markTestSkipped ('Symlinks are not available. ' );
340+ }
341+
342+ $ view = new View ($ root . DIRECTORY_SEPARATOR . 'views ' );
343+ $ view ->restrictToPath = true ;
344+
345+ try {
346+ $ this ->assertFalse ($ view ->exists ('alias ' ));
347+ $ this ->expectException (Exception::class);
348+ $ this ->expectExceptionMessage ('Template file is outside the views path. ' );
349+ $ view ->render ('alias ' );
350+ } finally {
351+ $ this ->removeDir ($ root );
352+ }
353+ }
354+
355+ public function testRestrictToPathKeepsNotFoundMessage (): void
356+ {
357+ $ this ->view ->restrictToPath = true ;
358+
359+ $ this ->assertFalse ($ this ->view ->exists ('badfile ' ));
360+ $ this ->expectException (Exception::class);
361+ $ this ->expectExceptionMessage ('Template file not found: ' );
362+ $ this ->view ->render ('badfile ' );
363+ }
364+
365+ private function makeOutsideTemplate (string $ content ): string
366+ {
367+ $ dir = sys_get_temp_dir () . DIRECTORY_SEPARATOR . 'flight-view- ' . uniqid ();
368+ mkdir ($ dir );
369+ $ file = $ dir . DIRECTORY_SEPARATOR . 'note.php ' ;
370+ file_put_contents ($ file , $ content );
371+
372+ return $ file ;
373+ }
374+
375+ private function removeDir (string $ dir ): void
376+ {
377+ foreach ((array ) scandir ($ dir ) as $ item ) {
378+ if ($ item === '. ' || $ item === '.. ' || $ item === false ) {
379+ continue ;
380+ }
381+
382+ $ path = $ dir . DIRECTORY_SEPARATOR . $ item ;
383+
384+ if (is_dir ($ path ) && !is_link ($ path )) {
385+ $ this ->removeDir ($ path );
386+ } else {
387+ unlink ($ path );
388+ }
389+ }
390+
391+ rmdir ($ dir );
392+ }
258393}
0 commit comments