@@ -23,17 +23,25 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
2323 protected $ cache = array ();
2424 protected $ errorCache = array ();
2525
26+ private $ useRealpath = false ;
2627 private $ rootPath ;
2728
2829 /**
2930 * @param string|array $paths A path or an array of paths where to look for templates
3031 * @param string|null $rootPath The root path common to all relative paths (null for getcwd())
3132 */
32- public function __construct ($ paths = array (), $ rootPath = null )
33+ public function __construct ($ paths = array (), $ rootPath = null , $ useRealpath = false )
3334 {
34- $ this ->rootPath = (null === $ rootPath ? getcwd () : $ rootPath ).DIRECTORY_SEPARATOR ;
35- if (false !== $ realPath = realpath ($ rootPath )) {
36- $ this ->rootPath = $ realPath .DIRECTORY_SEPARATOR ;
35+ $ this ->useRealpath = $ useRealpath ;
36+
37+ $ this ->rootPath = (null === $ rootPath ? getcwd () : $ rootPath );
38+ if ($ this ->rootPath ) {
39+ // realpath() usage for backward compatibility only
40+ if ($ useRealpath && false !== ($ realPath = realpath ($ this ->rootPath ))) {
41+ $ this ->rootPath = $ realPath .DIRECTORY_SEPARATOR ;
42+ } else {
43+ $ this ->rootPath = $ this ->normalizePath ($ this ->rootPath ).DIRECTORY_SEPARATOR ;
44+ }
3745 }
3846
3947 if ($ paths ) {
@@ -214,12 +222,12 @@ protected function findTemplate($name)
214222 $ path = $ this ->rootPath .'/ ' .$ path ;
215223 }
216224
217- if (is_file ($ path .'/ ' .$ shortname )) {
218- if (false !== $ realpath = realpath ($ path .'/ ' .$ shortname )) {
219- return $ this ->cache [$ name ] = $ realpath ;
225+ $ filename = $ path .'/ ' .$ shortname ;
226+ if (is_file ($ filename )) {
227+ if ($ this ->useRealpath && false !== ($ realPath = realpath ($ filename ))) {
228+ $ filename = $ realPath ;
220229 }
221-
222- return $ this ->cache [$ name ] = $ path .'/ ' .$ shortname ;
230+ return $ this ->cache [$ name ] = $ this ->normalizePath ($ filename );
223231 }
224232 }
225233
@@ -275,6 +283,39 @@ protected function validateName($name)
275283 }
276284 }
277285
286+ /**
287+ * Normalize a path by removing redundant '..' and thus preventing the need
288+ * of using the realpath() function that may come with some side effects
289+ *
290+ * @param string $string
291+ * @param bool $removeTrailingSlash
292+ * @return string
293+ */
294+ private function normalizePath ($ string , $ removeTrailingSlash = false )
295+ {
296+ if (DIRECTORY_SEPARATOR !== '/ ' ) { // Handle windows gracefully
297+ $ string = str_replace (DIRECTORY_SEPARATOR , '/ ' , $ string );
298+ }
299+
300+ // Preserve scheme
301+ // This leaves room for performance improvement
302+ $ scheme = null ;
303+ if (strpos ($ string , ':// ' )) {
304+ list ($ scheme , $ string ) = explode (':// ' , $ string , 2 );
305+ }
306+
307+ $ count = 0 ; // This leaves room for performance improvement too
308+ do {
309+ $ string = preg_replace ('@[^/]+/+..(/+|$)@ ' , '$2 ' , preg_replace ('@//+@ ' , '/ ' , $ string ), -1 , $ count );
310+ } while ($ count );
311+
312+ if ($ removeTrailingSlash ) {
313+ $ string = rtrim ($ string , '/ ' );
314+ }
315+
316+ return $ scheme ? ($ scheme .':// ' .$ string ) : $ string ;
317+ }
318+
278319 private function isAbsolutePath ($ file )
279320 {
280321 return strspn ($ file , '/ \\' , 0 , 1 )
0 commit comments