Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
51.72% covered (warning)
51.72%
15 / 29
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
loader
51.72% covered (warning)
51.72%
15 / 29
71.43% covered (warning)
71.43%
5 / 7
44.80
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSafeDirectories
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 addSafeDirectory
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSafeDirectories
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 findTemplate
43.75% covered (danger)
43.75%
7 / 16
0.00% covered (danger)
0.00%
0 / 1
15.72
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\template\twig;
15
16use phpbb\filesystem\helper as filesystem_helper;
17
18/**
19* Twig Template loader
20*/
21class loader extends \Twig\Loader\FilesystemLoader
22{
23    protected $safe_directories = array();
24
25    /**
26     * Constructor
27     *
28     * @param string|array    $paths
29     */
30    public function __construct($paths = array())
31    {
32        parent::__construct($paths, __DIR__);
33    }
34
35    /**
36    * Set safe directories
37    *
38    * @param array $directories Array of directories that are safe (empty to clear)
39    * @return \Twig\Loader\FilesystemLoader
40    */
41    public function setSafeDirectories($directories = array())
42    {
43        $this->safe_directories = array();
44
45        if (!empty($directories))
46        {
47            foreach ($directories as $directory)
48            {
49                $this->addSafeDirectory($directory);
50            }
51        }
52
53        return $this;
54    }
55
56    /**
57    * Add safe directory
58    *
59    * @param string $directory Directory that should be added
60    * @return \Twig\Loader\FilesystemLoader
61    */
62    public function addSafeDirectory($directory)
63    {
64        $directory = filesystem_helper::realpath($directory);
65
66        if ($directory !== false)
67        {
68            $this->safe_directories[] = $directory;
69        }
70
71        return $this;
72    }
73
74    /**
75    * Get current safe directories
76    *
77    * @return array
78    */
79    public function getSafeDirectories()
80    {
81        return $this->safe_directories;
82    }
83
84    /**
85    * Override for parent::validateName()
86    *
87    * This is done because we added support for safe directories, and when Twig
88    *    findTemplate() is called, validateName() is called first, which would
89    *    always throw an exception if the file is outside of the configured
90    *    template directories.
91    */
92    protected function validateName($name)
93    {
94        return;
95    }
96
97    /**
98     * Adds a realpath call to fix a BC break in Twig 1.26 (https://github.com/twigphp/Twig/issues/2145)
99     *
100     * {@inheritdoc}
101     */
102    public function addPath($path, $namespace = self::MAIN_NAMESPACE) : void
103    {
104        parent::addPath(filesystem_helper::realpath($path), $namespace);
105    }
106
107    /**
108     * Find the template
109     *
110     * Override for \Twig\Loader\FilesystemLoader::findTemplate
111     * to add support for loading from safe directories.
112     */
113    protected function findTemplate($name, $throw = true)
114    {
115        $name = (string) $name;
116
117        // normalize name
118        $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
119
120        // If this is in the cache we can skip the entire process below
121        //    as it should have already been validated
122        if (isset($this->cache[$name]))
123        {
124            return $this->cache[$name];
125        }
126
127        // First, find the template name. The override above of validateName
128        //    causes the validateName process to be skipped for this call
129        $file = parent::findTemplate($name, $throw);
130
131        try
132        {
133            // Try validating the name (which may throw an exception)
134            $this->validateName($name);
135        }
136        catch (\Twig\Error\LoaderError $e)
137        {
138            if (strpos($e->getRawMessage(), 'Looks like you try to load a template outside configured directories') === 0)
139            {
140                // Ok, so outside of the configured template directories, we
141                //    can now check if we're within a "safe" directory
142
143                // Find the real path of the directory the file is in
144                $directory = filesystem_helper::realpath(dirname($file));
145
146                if ($directory === false)
147                {
148                    // Some sort of error finding the actual path, must throw the exception
149                    throw $e;
150                }
151
152                foreach ($this->safe_directories as $safe_directory)
153                {
154                    if (strpos($directory, $safe_directory) === 0)
155                    {
156                        // The directory being loaded is below a directory
157                        // that is "safe". We're good to load it!
158                        return $file;
159                    }
160                }
161            }
162
163            // Not within any safe directories
164            throw $e;
165        }
166
167        // No exception from validateName, safe to load.
168        return $file;
169    }
170}