aboutsummaryrefslogtreecommitdiff
blob: 0f193dbe59f1a34e243664d59035eb9871fa1609 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/

namespace phpbb\template\twig;

/**
* Twig Template loader
*/
class loader extends \Twig_Loader_Filesystem
{
	protected $safe_directories = array();

	/**
	 * @var \phpbb\filesystem\filesystem_interface
	 */
	protected $filesystem;

	/**
	 * Constructor
	 *
	 * @param \phpbb\filesystem\filesystem_interface $filesystem
	 * @param string|array	$paths
	 */
	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $paths = array())
	{
		$this->filesystem = $filesystem;

		parent::__construct($paths, __DIR__);
	}

	/**
	* Set safe directories
	*
	* @param array $directories Array of directories that are safe (empty to clear)
	* @return \Twig_Loader_Filesystem
	*/
	public function setSafeDirectories($directories = array())
	{
		$this->safe_directories = array();

		if (!empty($directories))
		{
			foreach ($directories as $directory)
			{
				$this->addSafeDirectory($directory);
			}
		}

		return $this;
	}

	/**
	* Add safe directory
	*
	* @param string $directory Directory that should be added
	* @return \Twig_Loader_Filesystem
	*/
	public function addSafeDirectory($directory)
	{
		$directory = $this->filesystem->realpath($directory);

		if ($directory !== false)
		{
			$this->safe_directories[] = $directory;
		}

		return $this;
	}

	/**
	* Get current safe directories
	*
	* @return array
	*/
	public function getSafeDirectories()
	{
		return $this->safe_directories;
	}

	/**
	* Override for parent::validateName()
	*
	* This is done because we added support for safe directories, and when Twig
	*	findTemplate() is called, validateName() is called first, which would
	*	always throw an exception if the file is outside of the configured
	*	template directories.
	*/
	protected function validateName($name)
	{
		return;
	}

	/**
	 * Adds a realpath call to fix a BC break in Twig 1.26 (https://github.com/twigphp/Twig/issues/2145)
	 *
	 * {@inheritdoc}
	 */
	public function addPath($path, $namespace = self::MAIN_NAMESPACE)
	{
		return parent::addPath($this->filesystem->realpath($path), $namespace);
	}

	/**
	* Find the template
	*
	* Override for Twig_Loader_Filesystem::findTemplate to add support
	*	for loading from safe directories.
	*/
	protected function findTemplate($name, $throw = true)
	{
		$name = (string) $name;

		// normalize name
		$name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));

		// If this is in the cache we can skip the entire process below
		//	as it should have already been validated
		if (isset($this->cache[$name]))
		{
			return $this->cache[$name];
		}

		// First, find the template name. The override above of validateName
		//	causes the validateName process to be skipped for this call
		$file = parent::findTemplate($name, $throw);

		try
		{
			// Try validating the name (which may throw an exception)
			$this->validateName($name);
		}
		catch (\Twig_Error_Loader $e)
		{
			if (strpos($e->getRawMessage(), 'Looks like you try to load a template outside configured directories') === 0)
			{
				// Ok, so outside of the configured template directories, we
				//	can now check if we're within a "safe" directory

				// Find the real path of the directory the file is in
				$directory = $this->filesystem->realpath(dirname($file));

				if ($directory === false)
				{
					// Some sort of error finding the actual path, must throw the exception
					throw $e;
				}

				foreach ($this->safe_directories as $safe_directory)
				{
					if (strpos($directory, $safe_directory) === 0)
					{
						// The directory being loaded is below a directory
						// that is "safe". We're good to load it!
						return $file;
					}
				}
			}

			// Not within any safe directories
			throw $e;
		}

		// No exception from validateName, safe to load.
		return $file;
	}
}