Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F2752068
TitleBlacklist.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
TitleBlacklist.php
View Options
<?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
BadMethodCallException
;
use
MediaWiki\Content\TextContent
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\User
;
use
Wikimedia\AtEase\AtEase
;
/**
* @ingroup Extensions
*/
/**
* Implements a title blacklist for MediaWiki
*/
class
TitleBlacklist
{
/** @var TitleBlacklistEntry[]|null */
private
$mBlacklist
=
null
;
/** @var TitleBlacklistEntry[]|null */
private
$mWhitelist
=
null
;
/** @var TitleBlacklist|null */
protected
static
$instance
=
null
;
/** Increase this to invalidate the cached copies of both blacklist and whitelist */
public
const
VERSION
=
4
;
/**
* Get an instance of this class
*/
public
static
function
singleton
():
self
{
self
::
$instance
??=
new
self
();
return
self
::
$instance
;
}
/**
* Destroy/reset the current singleton instance.
*
* This is solely for testing and will fail unless MW_PHPUNIT_TEST is
* defined.
*/
public
static
function
destroySingleton
()
{
if
(
!
defined
(
'MW_PHPUNIT_TEST'
)
)
{
throw
new
BadMethodCallException
(
'Can not invoke '
.
__METHOD__
.
'() '
.
'out of tests (MW_PHPUNIT_TEST not set).'
);
}
self
::
$instance
=
null
;
}
/**
* Load all configured blacklist sources
*/
public
function
load
()
{
global
$wgTitleBlacklistSources
,
$wgTitleBlacklistCaching
;
$cache
=
MediaWikiServices
::
getInstance
()->
getMainWANObjectCache
();
// Try to find something in the cache
/** @var TitleBlacklistEntry[]|false $cachedBlacklist */
$cachedBlacklist
=
$cache
->
get
(
$cache
->
makeKey
(
'title_blacklist_entries'
)
);
if
(
$cachedBlacklist
&&
is_array
(
$cachedBlacklist
)
&&
$cachedBlacklist
[
0
]->
getFormatVersion
()
==
self
::
VERSION
)
{
$this
->
mBlacklist
=
$cachedBlacklist
;
return
;
}
$sources
=
$wgTitleBlacklistSources
;
$sources
[
'local'
]
=
[
'type'
=>
'message'
];
$this
->
mBlacklist
=
[];
foreach
(
$sources
as
$sourceName
=>
$source
)
{
$this
->
mBlacklist
=
array_merge
(
$this
->
mBlacklist
,
self
::
parseBlacklist
(
self
::
getBlacklistText
(
$source
),
$sourceName
)
);
}
$cache
->
set
(
$cache
->
makeKey
(
'title_blacklist_entries'
),
$this
->
mBlacklist
,
$wgTitleBlacklistCaching
[
'expiry'
]
);
wfDebugLog
(
'TitleBlacklist-cache'
,
'Updated '
.
$cache
->
makeKey
(
'title_blacklist_entries'
)
.
' with '
.
count
(
$this
->
mBlacklist
)
.
' entries.'
);
}
/**
* Load local whitelist
*/
public
function
loadWhitelist
()
{
global
$wgTitleBlacklistCaching
;
$cache
=
MediaWikiServices
::
getInstance
()->
getMainWANObjectCache
();
/** @var TitleBlacklistEntry[]|false $cachedWhitelist */
$cachedWhitelist
=
$cache
->
get
(
$cache
->
makeKey
(
'title_whitelist_entries'
)
);
if
(
$cachedWhitelist
&&
is_array
(
$cachedWhitelist
)
&&
$cachedWhitelist
[
0
]->
getFormatVersion
()
==
self
::
VERSION
)
{
$this
->
mWhitelist
=
$cachedWhitelist
;
return
;
}
$this
->
mWhitelist
=
self
::
parseBlacklist
(
wfMessage
(
'titlewhitelist'
)
->
inContentLanguage
()->
text
(),
'whitelist'
);
$cache
->
set
(
$cache
->
makeKey
(
'title_whitelist_entries'
),
$this
->
mWhitelist
,
$wgTitleBlacklistCaching
[
'expiry'
]
);
}
/**
* Get the text of a blacklist from a specified source
*
* @param array{type: string, src: ?string} $source A blacklist source from $wgTitleBlacklistSources
* @return string The content of the blacklist source as a string
*/
private
static
function
getBlacklistText
(
$source
)
{
if
(
!
is_array
(
$source
)
||
!
isset
(
$source
[
'type'
]
)
)
{
// Return empty string in error case
return
''
;
}
if
(
$source
[
'type'
]
===
'message'
)
{
return
wfMessage
(
'titleblacklist'
)->
inContentLanguage
()->
text
();
}
$src
=
$source
[
'src'
]
??
null
;
// All following types require the "src" element in the array
if
(
!
$src
)
{
return
''
;
}
if
(
$source
[
'type'
]
===
'localpage'
)
{
$title
=
Title
::
newFromText
(
$src
);
if
(
!
$title
)
{
return
''
;
}
if
(
$title
->
inNamespace
(
NS_MEDIAWIKI
)
)
{
$msg
=
wfMessage
(
$title
->
getText
()
)->
inContentLanguage
();
return
$msg
->
isDisabled
()
?
''
:
$msg
->
text
();
}
else
{
$page
=
MediaWikiServices
::
getInstance
()->
getWikiPageFactory
()->
newFromTitle
(
$title
);
if
(
$page
->
exists
()
)
{
$content
=
$page
->
getContent
();
return
(
$content
instanceof
TextContent
)
?
$content
->
getText
()
:
""
;
}
}
}
elseif
(
$source
[
'type'
]
===
'url'
)
{
return
self
::
getHttp
(
$src
);
}
elseif
(
$source
[
'type'
]
===
'file'
)
{
return
file_exists
(
$src
)
?
file_get_contents
(
$src
)
:
''
;
}
return
''
;
}
/**
* Parse blacklist from a string
*
* @param string $list Text of a blacklist source
* @param string $sourceName
* @return TitleBlacklistEntry[]
*/
public
static
function
parseBlacklist
(
$list
,
$sourceName
)
{
$lines
=
preg_split
(
"/
\r
?
\n
/"
,
$list
);
$result
=
[];
foreach
(
$lines
as
$line
)
{
$entry
=
TitleBlacklistEntry
::
newFromString
(
$line
,
$sourceName
);
if
(
$entry
)
{
$result
[]
=
$entry
;
}
}
return
$result
;
}
/**
* Check whether the blacklist restricts given user
* performing a specific action on the given Title
*
* @param Title $title Title to check
* @param User $user User to check
* @param string $action Action to check; 'edit' if unspecified
* @param bool $override If set to true, overrides work
* @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if
* blacklisted; otherwise false
*/
public
function
userCannot
(
$title
,
$user
,
$action
=
'edit'
,
$override
=
true
)
{
$entry
=
$this
->
isBlacklisted
(
$title
,
$action
);
if
(
!
$entry
)
{
return
false
;
}
$params
=
$entry
->
getParams
();
if
(
isset
(
$params
[
'autoconfirmed'
]
)
&&
$user
->
isAllowed
(
'autoconfirmed'
)
)
{
return
false
;
}
if
(
$override
&&
self
::
userCanOverride
(
$user
,
$action
)
)
{
return
false
;
}
return
$entry
;
}
/**
* Check whether the blacklist restricts
* performing a specific action on the given Title
*
* @param Title $title Title to check
* @param string $action Action to check; 'edit' if unspecified
* @return TitleBlacklistEntry|bool The corresponding TitleBlacklistEntry if blacklisted;
* otherwise FALSE
*/
public
function
isBlacklisted
(
$title
,
$action
=
'edit'
)
{
if
(
!(
$title
instanceof
Title
)
)
{
$title
=
Title
::
newFromText
(
$title
);
if
(
!
$title
)
{
// The fact that the page name is invalid will stop whatever
// action is going through. No sense in doing more work here.
return
false
;
}
}
$autoconfirmedItem
=
null
;
foreach
(
$this
->
getBlacklist
()
as
$item
)
{
if
(
$item
->
matches
(
$title
->
getFullText
(),
$action
)
)
{
if
(
$this
->
isWhitelisted
(
$title
,
$action
)
)
{
return
false
;
}
if
(
!
isset
(
$item
->
getParams
()[
'autoconfirmed'
]
)
)
{
return
$item
;
}
$autoconfirmedItem
??=
$item
;
}
}
return
$autoconfirmedItem
??
false
;
}
/**
* Check whether it has been explicitly whitelisted that the
* current User may perform a specific action on the given Title
*
* @param Title $title Title to check
* @param string $action Action to check; 'edit' if unspecified
* @return bool True if whitelisted; otherwise false
*/
public
function
isWhitelisted
(
$title
,
$action
=
'edit'
)
{
if
(
!(
$title
instanceof
Title
)
)
{
$title
=
Title
::
newFromText
(
$title
);
if
(
!
$title
)
{
return
false
;
}
}
$whitelist
=
$this
->
getWhitelist
();
foreach
(
$whitelist
as
$item
)
{
if
(
$item
->
matches
(
$title
->
getFullText
(),
$action
)
)
{
return
true
;
}
}
return
false
;
}
/**
* Get the current blacklist
*
* @return TitleBlacklistEntry[]
*/
public
function
getBlacklist
()
{
if
(
$this
->
mBlacklist
===
null
)
{
$this
->
load
();
}
return
$this
->
mBlacklist
;
}
/**
* Get the current whitelist
*
* @return TitleBlacklistEntry[]
*/
public
function
getWhitelist
()
{
if
(
$this
->
mWhitelist
===
null
)
{
$this
->
loadWhitelist
();
}
return
$this
->
mWhitelist
;
}
/**
* Get the text of a blacklist source via HTTP
*
* @param string $url URL of the blacklist source
* @return string The content of the blacklist source as a string
*/
private
static
function
getHttp
(
$url
)
{
global
$wgTitleBlacklistCaching
,
$wgMessageCacheType
;
// FIXME: This is a hack to use Memcached where possible (incl. WMF),
// but have CACHE_DB as fallback (instead of no cache).
// This might be a good candidate for T248005.
$services
=
MediaWikiServices
::
getInstance
();
$cache
=
$services
->
getObjectCacheFactory
()->
getInstance
(
$wgMessageCacheType
);
// Globally shared
$key
=
$cache
->
makeGlobalKey
(
'title_blacklist_source'
,
md5
(
$url
)
);
// Per-wiki
$warnkey
=
$cache
->
makeKey
(
'titleblacklistwarning'
,
md5
(
$url
)
);
$result
=
$cache
->
get
(
$key
);
$warn
=
$cache
->
get
(
$warnkey
);
if
(
!
is_string
(
$result
)
||
(
!
$warn
&&
!
mt_rand
(
0
,
$wgTitleBlacklistCaching
[
'warningchance'
]
)
)
)
{
$result
=
MediaWikiServices
::
getInstance
()->
getHttpRequestFactory
()
->
get
(
$url
,
[],
__METHOD__
);
$cache
->
set
(
$warnkey
,
1
,
$wgTitleBlacklistCaching
[
'warningexpiry'
]
);
$cache
->
set
(
$key
,
$result
,
$wgTitleBlacklistCaching
[
'expiry'
]
);
if
(
!
$result
)
{
wfDebugLog
(
'TitleBlacklist-cache'
,
"Error loading title blacklist from $url
\n
"
);
$result
=
''
;
}
}
return
$result
;
}
/**
* Invalidate the blacklist cache
*/
public
function
invalidate
()
{
$cache
=
MediaWikiServices
::
getInstance
()->
getMainWANObjectCache
();
$cache
->
delete
(
$cache
->
makeKey
(
'title_blacklist_entries'
)
);
}
/**
* Validate a new blacklist
*
* @suppress PhanParamSuspiciousOrder The preg_match() params are in the correct order
* @param TitleBlacklistEntry[] $blacklist
* @return string[] List of invalid entries; empty array means blacklist is valid
*/
public
function
validate
(
array
$blacklist
)
{
$badEntries
=
[];
foreach
(
$blacklist
as
$e
)
{
AtEase
::
suppressWarnings
();
$regex
=
$e
->
getRegex
();
// @phan-suppress-next-line SecurityCheck-ReDoS
if
(
preg_match
(
"/{$regex}/u"
,
''
)
===
false
)
{
$badEntries
[]
=
$e
->
getRaw
();
}
AtEase
::
restoreWarnings
();
}
return
$badEntries
;
}
/**
* Indicates whether user can override blacklist on certain action.
*
* @param User $user
* @param string $action
*
* @return bool
*/
public
static
function
userCanOverride
(
$user
,
$action
)
{
return
$user
->
isAllowed
(
'tboverride'
)
||
(
$action
==
'new-account'
&&
$user
->
isAllowed
(
'tboverride-account'
)
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Fri, Jul 3, 19:33 (1 d, 8 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
ce/54/c281decc84f2cc73fd6a58f05020
Default Alt Text
TitleBlacklist.php (10 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment