Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 106 |
TitleBlacklistEntry | |
0.00% |
0 / 1 |
|
0.00% |
0 / 11 |
2550 | |
0.00% |
0 / 105 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
filtersNewAccounts | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
matches | |
0.00% |
0 / 1 |
342 | |
0.00% |
0 / 34 |
|||
newFromString | |
0.00% |
0 / 1 |
380 | |
0.00% |
0 / 48 |
|||
getRegex | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getRaw | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getParams | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getCustomMessage | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
getFormatVersion | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 1 |
|||
setFormatVersion | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getErrorMessage | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 2 |
<?php | |
/** | |
* Title Blacklist class | |
* @author Victor Vasiliev | |
* @copyright © 2007-2010 Victor Vasiliev et al | |
* @license GPL-2.0-or-later | |
* @file | |
*/ | |
namespace MediaWiki\Extension\TitleBlacklist; | |
use CoreParserFunctions; | |
use Exception; | |
use ExtensionRegistry; | |
use MediaWiki\Extension\AntiSpoof\AntiSpoof; | |
use MediaWiki\MediaWikiServices; | |
use Wikimedia\AtEase\AtEase; | |
/** | |
* @ingroup Extensions | |
*/ | |
/** | |
* Represents a title blacklist entry | |
*/ | |
class TitleBlacklistEntry { | |
/** | |
* Raw line | |
* @var string | |
*/ | |
private $mRaw; | |
/** | |
* Regular expression to match | |
* @var string | |
*/ | |
private $mRegex; | |
/** | |
* Parameters for this entry | |
* @var array | |
*/ | |
private $mParams; | |
/** | |
* Entry format version | |
* @var int | |
*/ | |
private $mFormatVersion; | |
/** | |
* Source of this entry | |
* @var string | |
*/ | |
private $mSource; | |
/** | |
* @param string $regex Regular expression to match | |
* @param array $params Parameters for this entry | |
* @param string $raw Raw contents of this line | |
* @param string $source | |
*/ | |
private function __construct( $regex, $params, $raw, $source ) { | |
$this->mRaw = $raw; | |
$this->mRegex = $regex; | |
$this->mParams = $params; | |
$this->mFormatVersion = TitleBlacklist::VERSION; | |
$this->mSource = $source; | |
} | |
/** | |
* Returns whether this entry is capable of filtering new accounts. | |
* @return bool | |
*/ | |
private function filtersNewAccounts() { | |
global $wgTitleBlacklistUsernameSources; | |
if ( $wgTitleBlacklistUsernameSources === '*' ) { | |
return true; | |
} | |
if ( !$wgTitleBlacklistUsernameSources ) { | |
return false; | |
} | |
if ( !is_array( $wgTitleBlacklistUsernameSources ) ) { | |
throw new Exception( | |
'$wgTitleBlacklistUsernameSources must be "*", false or an array' ); | |
} | |
return in_array( $this->mSource, $wgTitleBlacklistUsernameSources, true ); | |
} | |
/** | |
* Check whether a user can perform the specified action on the specified Title | |
* | |
* @param string $title Title to check | |
* @param string $action Action to check | |
* @return bool TRUE if the regex matches the title, and is not overridden | |
* else false if it doesn't match (or was overridden) | |
*/ | |
public function matches( $title, $action ) { | |
if ( $title == '' ) { | |
return false; | |
} | |
if ( $action === 'new-account' && !$this->filtersNewAccounts() ) { | |
return false; | |
} | |
if ( isset( $this->mParams['antispoof'] ) | |
&& ExtensionRegistry::getInstance()->isLoaded( 'AntiSpoof' ) | |
) { | |
if ( $action === 'edit' ) { | |
// Use process cache for frequently edited pages | |
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); | |
$status = $cache->getWithSetCallback( | |
$cache->makeKey( 'titleblacklist', 'normalized-unicode-status', md5( $title ) ), | |
$cache::TTL_MONTH, | |
static function () use ( $title ) { | |
return AntiSpoof::checkUnicodeStringStatus( $title ); | |
}, | |
[ 'pcTTL' => $cache::TTL_PROC_LONG ] | |
); | |
} else { | |
$status = AntiSpoof::checkUnicodeStringStatus( $title ); | |
} | |
if ( $status->isOK() ) { | |
// Remove version from return value | |
list( , $title ) = explode( ':', $status->getValue(), 2 ); | |
} else { | |
wfDebugLog( 'TitleBlacklist', 'AntiSpoof could not normalize "' . $title . '" ' . | |
$status->getMessage( false, false, 'en' )->text() . '.' | |
); | |
} | |
} | |
AtEase::suppressWarnings(); | |
// @phan-suppress-next-line SecurityCheck-ReDoS | |
$match = preg_match( | |
"/^(?:{$this->mRegex})$/us" . ( isset( $this->mParams['casesensitive'] ) ? '' : 'i' ), | |
$title | |
); | |
AtEase::restoreWarnings(); | |
if ( $match ) { | |
if ( isset( $this->mParams['moveonly'] ) && $action != 'move' ) { | |
return false; | |
} | |
if ( isset( $this->mParams['newaccountonly'] ) && $action != 'new-account' ) { | |
return false; | |
} | |
if ( !isset( $this->mParams['noedit'] ) && $action == 'edit' ) { | |
return false; | |
} | |
if ( isset( $this->mParams['reupload'] ) && $action == 'upload' ) { | |
// Special:Upload also checks 'create' permissions when not reuploading | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Create a new TitleBlacklistEntry from a line of text | |
* | |
* @param string $line String containing a line of blacklist text | |
* @param string $source | |
* @return TitleBlacklistEntry|null | |
*/ | |
public static function newFromString( $line, $source ) { | |
// Keep line for raw data | |
$raw = $line; | |
$options = []; | |
// Strip comments | |
$line = preg_replace( "/^\\s*([^#]*)\\s*((.*)?)$/", "\\1", $line ); | |
$line = trim( $line ); | |
// A blank string causes problems later on | |
if ( $line === '' ) { | |
return null; | |
} | |
// Parse the rest of message | |
$pockets = []; | |
if ( !preg_match( '/^(.*?)(\s*<([^<>]*)>)?$/', $line, $pockets ) ) { | |
return null; | |
} | |
$regex = trim( $pockets[1] ); | |
// We'll be matching against text form | |
$regex = str_replace( '_', ' ', $regex ); | |
$opts_str = isset( $pockets[3] ) ? trim( $pockets[3] ) : ''; | |
// Parse opts | |
$opts = preg_split( '/\s*\|\s*/', $opts_str ); | |
foreach ( $opts as $opt ) { | |
$opt2 = strtolower( $opt ); | |
if ( $opt2 == 'autoconfirmed' ) { | |
$options['autoconfirmed'] = true; | |
} | |
if ( $opt2 == 'moveonly' ) { | |
$options['moveonly'] = true; | |
} | |
if ( $opt2 == 'newaccountonly' ) { | |
$options['newaccountonly'] = true; | |
} | |
if ( $opt2 == 'noedit' ) { | |
$options['noedit'] = true; | |
} | |
if ( $opt2 == 'casesensitive' ) { | |
$options['casesensitive'] = true; | |
} | |
if ( $opt2 == 'reupload' ) { | |
$options['reupload'] = true; | |
} | |
if ( preg_match( '/errmsg\s*=\s*(.+)/i', $opt, $matches ) ) { | |
$options['errmsg'] = $matches[1]; | |
} | |
if ( $opt2 == 'antispoof' ) { | |
$options['antispoof'] = true; | |
} | |
} | |
// Process magic words | |
preg_match_all( '/{{\s*([a-z]+)\s*:\s*(.+?)\s*}}/', $regex, $magicwords, PREG_SET_ORDER ); | |
foreach ( $magicwords as $mword ) { | |
switch ( strtolower( $mword[1] ) ) { | |
case 'ns': | |
$cpf_result = CoreParserFunctions::ns( | |
MediaWikiServices::getInstance()->getParser(), | |
$mword[2] | |
); | |
if ( is_string( $cpf_result ) ) { | |
// All result will have the same value, so we can just use str_replace() | |
$regex = str_replace( $mword[0], $cpf_result, $regex ); | |
} | |
break; | |
case 'int': | |
$cpf_result = wfMessage( $mword[2] )->inContentLanguage()->text(); | |
if ( is_string( $cpf_result ) ) { | |
$regex = str_replace( $mword[0], $cpf_result, $regex ); | |
} | |
} | |
} | |
// Return result | |
if ( $regex ) { | |
// @phan-suppress-next-line SecurityCheck-ReDoS | |
return new TitleBlacklistEntry( $regex, $options, $raw, $source ); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* @return string This entry's regular expression | |
*/ | |
public function getRegex() { | |
return $this->mRegex; | |
} | |
/** | |
* @return string This entry's raw line | |
*/ | |
public function getRaw() { | |
return $this->mRaw; | |
} | |
/** | |
* @return array This entry's parameters | |
*/ | |
public function getParams() { | |
return $this->mParams; | |
} | |
/** | |
* @return string Custom message for this entry | |
*/ | |
public function getCustomMessage() { | |
return $this->mParams['errmsg'] ?? null; | |
} | |
/** | |
* @return int The format version | |
*/ | |
public function getFormatVersion() { | |
return $this->mFormatVersion; | |
} | |
/** | |
* Set the format version | |
* | |
* @param int $v New version to set | |
*/ | |
public function setFormatVersion( $v ) { | |
$this->mFormatVersion = $v; | |
} | |
/** | |
* Return the error message name for the blacklist entry. | |
* | |
* @param string $operation Operation name (as in titleblacklist-forbidden message name) | |
* | |
* @return string The error message name | |
*/ | |
public function getErrorMessage( $operation ) { | |
$message = $this->getCustomMessage(); | |
// For grep: | |
// titleblacklist-forbidden-edit, titleblacklist-forbidden-move, | |
// titleblacklist-forbidden-upload, titleblacklist-forbidden-new-account | |
return $message ?: "titleblacklist-forbidden-{$operation}"; | |
} | |
} | |
class_alias( TitleBlacklistEntry::class, 'TitleBlacklistEntry' ); |