Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
EscaperExtension.php
001 <?php
002
003 /*
004 * This file is part of Twig.
005 *
006 * (c) Fabien Potencier
007 *
008 * For the full copyright and license information, please view the LICENSE
009 * file that was distributed with this source code.
010 */
011
012 namespace Twig\Extension {
013 use Twig\FileExtensionEscapingStrategy;
014 use Twig\NodeVisitor\EscaperNodeVisitor;
015 use Twig\TokenParser\AutoEscapeTokenParser;
016 use Twig\TwigFilter;
017
018 final class EscaperExtension extends AbstractExtension
019 {
020 private $defaultStrategy;
021 private $escapers = [];
022
023 /** @internal */
024 public $safeClasses = [];
025
026 /** @internal */
027 public $safeLookup = [];
028
029 /**
030 * @param string|false|callable $defaultStrategy An escaping strategy
031 *
032 * @see setDefaultStrategy()
033 */
034 public function __construct($defaultStrategy = 'html')
035 {
036 $this->setDefaultStrategy($defaultStrategy);
037 }
038
039 public function getTokenParsers()
040 {
041 return [new AutoEscapeTokenParser()];
042 }
043
044 public function getNodeVisitors()
045 {
046 return [new EscaperNodeVisitor()];
047 }
048
049 public function getFilters()
050 {
051 return [
052 new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
053 new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
054 new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
055 ];
056 }
057
058 /**
059 * Sets the default strategy to use when not defined by the user.
060 *
061 * The strategy can be a valid PHP callback that takes the template
062 * name as an argument and returns the strategy to use.
063 *
064 * @param string|false|callable $defaultStrategy An escaping strategy
065 */
066 public function setDefaultStrategy($defaultStrategy)
067 {
068 if ('name' === $defaultStrategy) {
069 $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
070 }
071
072 $this->defaultStrategy = $defaultStrategy;
073 }
074
075 /**
076 * Gets the default strategy to use when not defined by the user.
077 *
078 * @param string $name The template name
079 *
080 * @return string|false The default strategy to use for the template
081 */
082 public function getDefaultStrategy($name)
083 {
084 // disable string callables to avoid calling a function named html or js,
085 // or any other upcoming escaping strategy
086 if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) {
087 return \call_user_func($this->defaultStrategy, $name);
088 }
089
090 return $this->defaultStrategy;
091 }
092
093 /**
094 * Defines a new escaper to be used via the escape filter.
095 *
096 * @param string $strategy The strategy name that should be used as a strategy in the escape call
097 * @param callable $callable A valid PHP callable
098 */
099 public function setEscaper($strategy, callable $callable)
100 {
101 $this->escapers[$strategy] = $callable;
102 }
103
104 /**
105 * Gets all defined escapers.
106 *
107 * @return callable[] An array of escapers
108 */
109 public function getEscapers()
110 {
111 return $this->escapers;
112 }
113
114 public function setSafeClasses(array $safeClasses = [])
115 {
116 $this->safeClasses = [];
117 $this->safeLookup = [];
118 foreach ($safeClasses as $class => $strategies) {
119 $this->addSafeClass($class, $strategies);
120 }
121 }
122
123 public function addSafeClass(string $class, array $strategies)
124 {
125 $class = ltrim($class, '\\');
126 if (!isset($this->safeClasses[$class])) {
127 $this->safeClasses[$class] = [];
128 }
129 $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
130
131 foreach ($strategies as $strategy) {
132 $this->safeLookup[$strategy][$class] = true;
133 }
134 }
135 }
136
137 class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper');
138 }
139
140 namespace {
141 use Twig\Environment;
142 use Twig\Error\RuntimeError;
143 use Twig\Extension\CoreExtension;
144 use Twig\Extension\EscaperExtension;
145 use Twig\Markup;
146 use Twig\Node\Expression\ConstantExpression;
147 use Twig\Node\Node;
148
149 /**
150 * Marks a variable as being safe.
151 *
152 * @param string $string A PHP variable
153 *
154 * @return string
155 */
156 function twig_raw_filter($string)
157 {
158 return $string;
159 }
160
161 /**
162 * Escapes a string.
163 *
164 * @param mixed $string The value to be escaped
165 * @param string $strategy The escaping strategy
166 * @param string $charset The charset
167 * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
168 *
169 * @return string
170 */
171 function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
172 {
173 if ($autoescape && $string instanceof Markup) {
174 return $string;
175 }
176
177 if (!\is_string($string)) {
178 if (\is_object($string) && method_exists($string, '__toString')) {
179 if ($autoescape) {
180 $c = \get_class($string);
181 $ext = $env->getExtension(EscaperExtension::class);
182 if (!isset($ext->safeClasses[$c])) {
183 $ext->safeClasses[$c] = [];
184 foreach (class_parents($string) + class_implements($string) as $class) {
185 if (isset($ext->safeClasses[$class])) {
186 $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
187 foreach ($ext->safeClasses[$class] as $s) {
188 $ext->safeLookup[$s][$c] = true;
189 }
190 }
191 }
192 }
193 if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
194 return (string) $string;
195 }
196 }
197
198 $string = (string) $string;
199 } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
200 return $string;
201 }
202 }
203
204 if ('' === $string) {
205 return '';
206 }
207
208 if (null === $charset) {
209 $charset = $env->getCharset();
210 }
211
212 switch ($strategy) {
213 case 'html':
214 // see https://www.php.net/htmlspecialchars
215
216 // Using a static variable to avoid initializing the array
217 // each time the function is called. Moving the declaration on the
218 // top of the function slow downs other escaping strategies.
219 static $htmlspecialcharsCharsets = [
220 'ISO-8859-1' => true, 'ISO8859-1' => true,
221 'ISO-8859-15' => true, 'ISO8859-15' => true,
222 'utf-8' => true, 'UTF-8' => true,
223 'CP866' => true, 'IBM866' => true, '866' => true,
224 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
225 '1251' => true,
226 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
227 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
228 'BIG5' => true, '950' => true,
229 'GB2312' => true, '936' => true,
230 'BIG5-HKSCS' => true,
231 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
232 'EUC-JP' => true, 'EUCJP' => true,
233 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
234 ];
235
236 if (isset($htmlspecialcharsCharsets[$charset])) {
237 return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
238 }
239
240 if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
241 // cache the lowercase variant for future iterations
242 $htmlspecialcharsCharsets[$charset] = true;
243
244 return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
245 }
246
247 $string = twig_convert_encoding($string, 'UTF-8', $charset);
248 $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
249
250 return iconv('UTF-8', $charset, $string);
251
252 case 'js':
253 // escape all non-alphanumeric characters
254 // into their \x or \uHHHH representations
255 if ('UTF-8' !== $charset) {
256 $string = twig_convert_encoding($string, 'UTF-8', $charset);
257 }
258
259 if (!preg_match('//u', $string)) {
260 throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
261 }
262
263 $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
264 $char = $matches[0];
265
266 /*
267 * A few characters have short escape sequences in JSON and JavaScript.
268 * Escape sequences supported only by JavaScript, not JSON, are omitted.
269 * \" is also supported but omitted, because the resulting string is not HTML safe.
270 */
271 static $shortMap = [
272 '\\' => '\\\\',
273 '/' => '\\/',
274 "\x08" => '\b',
275 "\x0C" => '\f',
276 "\x0A" => '\n',
277 "\x0D" => '\r',
278 "\x09" => '\t',
279 ];
280
281 if (isset($shortMap[$char])) {
282 return $shortMap[$char];
283 }
284
285 $codepoint = mb_ord($char, 'UTF-8');
286 if (0x10000 > $codepoint) {
287 return sprintf('\u%04X', $codepoint);
288 }
289
290 // Split characters outside the BMP into surrogate pairs
291 // https://tools.ietf.org/html/rfc2781.html#section-2.1
292 $u = $codepoint - 0x10000;
293 $high = 0xD800 | ($u >> 10);
294 $low = 0xDC00 | ($u & 0x3FF);
295
296 return sprintf('\u%04X\u%04X', $high, $low);
297 }, $string);
298
299 if ('UTF-8' !== $charset) {
300 $string = iconv('UTF-8', $charset, $string);
301 }
302
303 return $string;
304
305 case 'css':
306 if ('UTF-8' !== $charset) {
307 $string = twig_convert_encoding($string, 'UTF-8', $charset);
308 }
309
310 if (!preg_match('//u', $string)) {
311 throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
312 }
313
314 $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
315 $char = $matches[0];
316
317 return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
318 }, $string);
319
320 if ('UTF-8' !== $charset) {
321 $string = iconv('UTF-8', $charset, $string);
322 }
323
324 return $string;
325
326 case 'html_attr':
327 if ('UTF-8' !== $charset) {
328 $string = twig_convert_encoding($string, 'UTF-8', $charset);
329 }
330
331 if (!preg_match('//u', $string)) {
332 throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
333 }
334
335 $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
336 /**
337 * This function is adapted from code coming from Zend Framework.
338 *
339 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
340 * @license https://framework.zend.com/license/new-bsd New BSD License
341 */
342 $chr = $matches[0];
343 $ord = \ord($chr);
344
345 /*
346 * The following replaces characters undefined in HTML with the
347 * hex entity for the Unicode replacement character.
348 */
349 if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
350 return '�';
351 }
352
353 /*
354 * Check if the current character to escape has a name entity we should
355 * replace it with while grabbing the hex value of the character.
356 */
357 if (1 === \strlen($chr)) {
358 /*
359 * While HTML supports far more named entities, the lowest common denominator
360 * has become HTML5's XML Serialisation which is restricted to the those named
361 * entities that XML supports. Using HTML entities would result in this error:
362 * XML Parsing Error: undefined entity
363 */
364 static $entityMap = [
365 34 => '"', /* quotation mark */
366 38 => '&', /* ampersand */
367 60 => '<', /* less-than sign */
368 62 => '>', /* greater-than sign */
369 ];
370
371 if (isset($entityMap[$ord])) {
372 return $entityMap[$ord];
373 }
374
375 return sprintf('&#x%02X;', $ord);
376 }
377
378 /*
379 * Per OWASP recommendations, we'll use hex entities for any other
380 * characters where a named entity does not exist.
381 */
382 return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
383 }, $string);
384
385 if ('UTF-8' !== $charset) {
386 $string = iconv('UTF-8', $charset, $string);
387 }
388
389 return $string;
390
391 case 'url':
392 return rawurlencode($string);
393
394 default:
395 // check the ones set on CoreExtension for BC (to be removed in 3.0)
396 $legacyEscapers = $env->getExtension(CoreExtension::class)->getEscapers(false);
397 if (array_key_exists($strategy, $legacyEscapers)) {
398 return $legacyEscapers[$strategy]($env, $string, $charset);
399 }
400
401 $escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
402 if (array_key_exists($strategy, $escapers)) {
403 return $escapers[$strategy]($env, $string, $charset);
404 }
405
406 $escapers = array_merge($legacyEscapers, $escapers);
407 $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
408
409 throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
410 }
411 }
412
413 /**
414 * @internal
415 */
416 function twig_escape_filter_is_safe(Node $filterArgs)
417 {
418 foreach ($filterArgs as $arg) {
419 if ($arg instanceof ConstantExpression) {
420 return [$arg->getAttribute('value')];
421 }
422
423 return [];
424 }
425
426 return ['html'];
427 }
428 }
429