mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/TemplateStyles
synced 2024-12-02 20:06:34 +00:00
3b337af58d
Change-Id: I7f64539f1c9c4c03088e28e99f77a9acc503f627
90 lines
2.2 KiB
PHP
90 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Extension\TemplateStyles;
|
|
|
|
/**
|
|
* @file
|
|
* @license GPL-2.0-or-later
|
|
*/
|
|
|
|
use Wikimedia\CSS\Grammar\TokenMatcher;
|
|
use Wikimedia\CSS\Grammar\UrlMatcher;
|
|
use Wikimedia\CSS\Objects\Token;
|
|
|
|
/**
|
|
* Extend the standard factory for TemplateStyles-specific matchers
|
|
*/
|
|
class TemplateStylesMatcherFactory extends \Wikimedia\CSS\Grammar\MatcherFactory {
|
|
|
|
/** @var array URL validation regexes */
|
|
protected $allowedDomains;
|
|
|
|
/**
|
|
* @param array $allowedDomains See $wgTemplateStylesAllowedUrls
|
|
*/
|
|
public function __construct( array $allowedDomains ) {
|
|
$this->allowedDomains = $allowedDomains;
|
|
}
|
|
|
|
/**
|
|
* Check a URL for safety
|
|
* @param string $type
|
|
* @param string $url
|
|
* @return bool
|
|
*/
|
|
protected function checkUrl( $type, $url ) {
|
|
// Undo unnecessary percent encoding
|
|
$url = preg_replace_callback( '/%[2-7][0-9A-Fa-f]/', static function ( $m ) {
|
|
$char = urldecode( $m[0] );
|
|
/** @phan-suppress-next-line PhanParamSuspiciousOrder */
|
|
if ( strpos( '"#%<>[\]^`{|}/?&=+;', $char ) === false ) {
|
|
# Unescape it
|
|
return $char;
|
|
}
|
|
return $m[0];
|
|
}, $url );
|
|
|
|
// Don't allow unescaped \ or /../ in the non-query part of the URL
|
|
$tmp = preg_replace( '<[#?].*$>', '', $url );
|
|
if ( strpos( $tmp, '\\' ) !== false || preg_match( '<(?:^|/|%2[fF])\.+(?:/|%2[fF]|$)>', $tmp ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Check if it is allowed
|
|
$regexes = $this->allowedDomains[$type] ?? [];
|
|
foreach ( $regexes as $regex ) {
|
|
if ( preg_match( $regex, $url ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function urlstring( $type ) {
|
|
$key = __METHOD__ . ':' . $type;
|
|
if ( !isset( $this->cache[$key] ) ) {
|
|
$this->cache[$key] = new TokenMatcher( Token::T_STRING, function ( Token $t ) use ( $type ) {
|
|
return $this->checkUrl( $type, $t->value() );
|
|
} );
|
|
}
|
|
return $this->cache[$key];
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function url( $type ) {
|
|
$key = __METHOD__ . ':' . $type;
|
|
if ( !isset( $this->cache[$key] ) ) {
|
|
$this->cache[$key] = new UrlMatcher( function ( $url, $modifiers ) use ( $type ) {
|
|
return !$modifiers && $this->checkUrl( $type, $url );
|
|
} );
|
|
}
|
|
return $this->cache[$key];
|
|
}
|
|
}
|