Version in base suite: 1.31.10-1~deb10u1 Base version: mediawiki_1.31.10-1~deb10u1 Target version: mediawiki_1.31.12-1~deb10u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/m/mediawiki/mediawiki_1.31.10-1~deb10u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/m/mediawiki/mediawiki_1.31.12-1~deb10u1.dsc RELEASE-NOTES-1.31 | 32 ++++ composer.json | 1 debian/changelog | 10 + debian/salsa-ci.yml | 7 + extensions/CategoryTree/composer.json | 2 extensions/CategoryTree/includes/CategoryTreeHooks.php | 2 extensions/Cite/composer.json | 2 extensions/CiteThisPage/composer.json | 2 extensions/CodeEditor/composer.json | 2 extensions/ConfirmEdit/ReCaptchaNoCaptcha/ReCaptchaNoCaptcha.class.php | 2 extensions/ConfirmEdit/captcha-old.py | 2 extensions/ConfirmEdit/captcha.py | 6 extensions/Gadgets/composer.json | 2 extensions/ImageMap/composer.json | 2 extensions/InputBox/composer.json | 2 extensions/Interwiki/composer.json | 2 extensions/LocalisationUpdate/composer.json | 2 extensions/MultimediaViewer/composer.json | 2 extensions/Nuke/composer.json | 2 extensions/ParserFunctions/composer.json | 2 extensions/ParserFunctions/includes/ExtParserFunctions.php | 2 extensions/PdfHandler/composer.json | 2 extensions/Poem/composer.json | 2 extensions/Renameuser/composer.json | 2 extensions/ReplaceText/composer.json | 2 extensions/ReplaceText/src/ReplaceTextUtils.php | 2 extensions/SpamBlacklist/composer.json | 2 extensions/SpamBlacklist/includes/SpamBlacklist.php | 2 extensions/SyntaxHighlight_GeSHi/composer.json | 2 extensions/TitleBlacklist/composer.json | 2 extensions/WikiEditor/composer.json | 2 includes/Defines.php | 2 includes/EditPage.php | 10 + includes/OutputPage.php | 4 includes/libs/composer/ComposerJson.php | 4 includes/libs/objectcache/MemcachedClient.php | 7 - includes/logging/BlockLogFormatter.php | 11 - includes/media/FormatMetadata.php | 7 - includes/page/Article.php | 8 + includes/profiler/SectionProfiler.php | 12 + includes/skins/BaseTemplate.php | 2 includes/skins/SkinTemplate.php | 12 + includes/specials/SpecialBotPasswords.php | 12 - includes/specials/SpecialContributions.php | 22 ++- includes/specials/SpecialLog.php | 12 + includes/specials/SpecialUserrights.php | 8 - includes/specials/helpers/LoginHelper.php | 2 includes/user/BotPassword.php | 67 +++++++--- includes/user/CentralIdLookup.php | 2 includes/user/User.php | 18 ++ languages/Language.php | 4 languages/classes/LanguageFi.php | 2 languages/i18n/en.json | 2 languages/i18n/qqq.json | 2 skins/MonoBook/composer.json | 2 skins/Timeless/composer.json | 2 skins/Vector/composer.json | 2 tests/phan/bin/postprocess-phan.php | 4 tests/phpunit/includes/user/BotPasswordTest.php | 62 ++++++++- 59 files changed, 325 insertions(+), 87 deletions(-) diff -Nru mediawiki-1.31.10/RELEASE-NOTES-1.31 mediawiki-1.31.12/RELEASE-NOTES-1.31 --- mediawiki-1.31.10/RELEASE-NOTES-1.31 2020-09-24 21:51:25.565653600 +0000 +++ mediawiki-1.31.12/RELEASE-NOTES-1.31 2020-12-18 02:50:09.360964500 +0000 @@ -1,3 +1,35 @@ +== MediaWiki 1.31.12 == + +This is a maintenance release of the MediaWiki 1.31 branch. + +=== Changes since MediaWiki 1.31.11 === +* Fixed issues relating to User::isRegistered() not existing in 1.31. + +== MediaWiki 1.31.11 == + +This is a security and maintenance release of the MediaWiki 1.31 branch. + +=== Changes since MediaWiki 1.31.10 === +* Fix undefined $wgRedirectOnLogin. +* (T251661, T265313) CentralIdLookup::factoryNonLocal can return null. +* (T263592) media: Fix case of FlashPixVersion in + FormatMetadata::makeFormattedData(). +* (T265223) BaseTemplate: Guard against passing zero arg to array_merge(). +* (T266418) composer.json: add requirement for composer-plugin-api ^1.1. +* (T260631, T260633), BotPassword::save() now returns a Status object for the + result rather than a bool. The length of the bot password grants and + restriction fields are now validated, and an error will be thrown if it + would be truncated by the database. +* (T264536, T233012) SectionProfiler: Do not attempt to use null values as arrays. +* (T269178) MemcachedClient: Cast Resource to integer. +* (T268917, CVE-2020-35475) SECURITY: Use Xml::element in SpecialUserrights for + sanity. +* (T268938, CVE-2020-35479) SECURITY: BlockLogFormatter can output raw html. +* (T205908, CVE-2020-35477) SECURITY: Unable to change visibility of log entries + when MediaWiki:Mainpage uses Special:MyLanguage. +* (T120883, CVE-2020-35480) SECURITY: Divergent behavior for contributions and + user pages of hidden users and missing users. + == MediaWiki 1.31.10 == This is a maintenance release of the MediaWiki 1.31 branch. diff -Nru mediawiki-1.31.10/composer.json mediawiki-1.31.12/composer.json --- mediawiki-1.31.10/composer.json 2020-09-24 21:51:25.565653600 +0000 +++ mediawiki-1.31.12/composer.json 2020-12-18 01:30:56.047493700 +0000 @@ -16,6 +16,7 @@ "wiki": "https://www.mediawiki.org/" }, "require": { + "composer-plugin-api": "^1.1", "composer/semver": "1.4.2", "cssjanus/cssjanus": "1.3.0", "ext-ctype": "*", diff -Nru mediawiki-1.31.10/debian/changelog mediawiki-1.31.12/debian/changelog --- mediawiki-1.31.10/debian/changelog 2020-09-24 22:29:07.000000000 +0000 +++ mediawiki-1.31.12/debian/changelog 2020-12-17 23:30:11.000000000 +0000 @@ -1,3 +1,13 @@ +mediawiki (1:1.31.12-1~deb10u1) buster-security; urgency=medium + + * New upstream version 1.31.12, fixing CVE-2020-35475, CVE-2020-35477, + CVE-2020-35479, CVE-2020-35480. + This version is not affected by CVE-2020-35474 nor CVE-2020-35478. + * Respect $wgRedirectOnLogin configuration setting (Closes: #971986). + * Flatten footer links without triggering a PHP warning (Closes: #971985). + + -- Kunal Mehta Thu, 17 Dec 2020 15:30:11 -0800 + mediawiki (1:1.31.10-1~deb10u1) buster-security; urgency=medium * New upstream version 1.31.10, fixing CVE-2020-15005, diff -Nru mediawiki-1.31.10/debian/salsa-ci.yml mediawiki-1.31.12/debian/salsa-ci.yml --- mediawiki-1.31.10/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ mediawiki-1.31.12/debian/salsa-ci.yml 2020-12-17 23:30:11.000000000 +0000 @@ -0,0 +1,7 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + +variables: + RELEASE: 'buster' diff -Nru mediawiki-1.31.10/extensions/CategoryTree/composer.json mediawiki-1.31.12/extensions/CategoryTree/composer.json --- mediawiki-1.31.10/extensions/CategoryTree/composer.json 2020-09-24 21:51:26.429666000 +0000 +++ mediawiki-1.31.12/extensions/CategoryTree/composer.json 2020-12-18 02:50:58.533518300 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/CategoryTree/includes/CategoryTreeHooks.php mediawiki-1.31.12/extensions/CategoryTree/includes/CategoryTreeHooks.php --- mediawiki-1.31.10/extensions/CategoryTree/includes/CategoryTreeHooks.php 2020-09-24 21:51:26.437666200 +0000 +++ mediawiki-1.31.12/extensions/CategoryTree/includes/CategoryTreeHooks.php 2020-12-18 02:50:58.541518200 +0000 @@ -137,7 +137,7 @@ * This loads CategoryTreeFunctions.php and calls CategoryTree::getTag() * @param string $cat * @param array $argv - * @param Parser $parser + * @param Parser|null $parser * @param bool $allowMissing * @return bool|string */ diff -Nru mediawiki-1.31.10/extensions/Cite/composer.json mediawiki-1.31.12/extensions/Cite/composer.json --- mediawiki-1.31.10/extensions/Cite/composer.json 2020-09-24 21:51:26.457666400 +0000 +++ mediawiki-1.31.12/extensions/Cite/composer.json 2020-12-18 02:50:58.573518800 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/CiteThisPage/composer.json mediawiki-1.31.12/extensions/CiteThisPage/composer.json --- mediawiki-1.31.10/extensions/CiteThisPage/composer.json 2020-09-24 21:51:26.501667000 +0000 +++ mediawiki-1.31.12/extensions/CiteThisPage/composer.json 2020-12-18 02:50:58.621519300 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/CodeEditor/composer.json mediawiki-1.31.12/extensions/CodeEditor/composer.json --- mediawiki-1.31.10/extensions/CodeEditor/composer.json 2020-09-24 21:51:26.521667200 +0000 +++ mediawiki-1.31.12/extensions/CodeEditor/composer.json 2020-12-18 02:50:58.649519400 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/ConfirmEdit/ReCaptchaNoCaptcha/ReCaptchaNoCaptcha.class.php mediawiki-1.31.12/extensions/ConfirmEdit/ReCaptchaNoCaptcha/ReCaptchaNoCaptcha.class.php --- mediawiki-1.31.10/extensions/ConfirmEdit/ReCaptchaNoCaptcha/ReCaptchaNoCaptcha.class.php 2020-09-24 21:51:26.561668000 +0000 +++ mediawiki-1.31.12/extensions/ConfirmEdit/ReCaptchaNoCaptcha/ReCaptchaNoCaptcha.class.php 2020-12-18 02:50:58.789521200 +0000 @@ -109,7 +109,7 @@ $data['remoteip'] = $wgRequest->getIP(); } $url = wfAppendQuery( $url, $data ); - $request = MWHttpRequest::factory( $url, [ 'method' => 'GET' ] ); + $request = MWHttpRequest::factory( $url, [ 'method' => 'POST' ] ); $status = $request->execute(); if ( !$status->isOK() ) { $this->error = 'http'; diff -Nru mediawiki-1.31.10/extensions/ConfirmEdit/captcha-old.py mediawiki-1.31.12/extensions/ConfirmEdit/captcha-old.py --- mediawiki-1.31.10/extensions/ConfirmEdit/captcha-old.py 2020-09-24 13:59:37.358836700 +0000 +++ mediawiki-1.31.12/extensions/ConfirmEdit/captcha-old.py 2020-11-10 21:04:06.258586600 +0000 @@ -209,7 +209,7 @@ script_dir = os.path.dirname(os.path.realpath(__file__)) parser = OptionParser() parser.add_option("--wordlist", help="A list of words (required)", metavar="WORDS.txt") - parser.add_option("--random", help="Use random charcters instead of a wordlist", action="store_true") + parser.add_option("--random", help="Use random characters instead of a wordlist", action="store_true") parser.add_option("--key", help="The passphrase set as $wgCaptchaSecret (required)", metavar="KEY") parser.add_option("--output", help="The directory to put the images in - $wgCaptchaDirectory (required)", metavar="DIR") parser.add_option("--font", help="The font to use (required)", metavar="FONT.ttf") diff -Nru mediawiki-1.31.10/extensions/ConfirmEdit/captcha.py mediawiki-1.31.12/extensions/ConfirmEdit/captcha.py --- mediawiki-1.31.10/extensions/ConfirmEdit/captcha.py 2020-09-24 13:59:37.358836700 +0000 +++ mediawiki-1.31.12/extensions/ConfirmEdit/captcha.py 2020-11-10 21:04:06.258586600 +0000 @@ -108,13 +108,13 @@ # Create noise nblock = 4 - nsize = (im.size[0] / nblock, im.size[1] / nblock) + nsize = (im.size[0] // nblock, im.size[1] // nblock) noise = Image.new('L', nsize, bgcolor) data = noise.load() for x in range(nsize[0]): for y in range(nsize[1]): r = random.randint(0, 65) - gradient = 70 * x / nsize[0] + gradient = 70 * x // nsize[0] data[x, y] = r + gradient # Turn speckles into blobs noise = noise.resize(im.size, Image.BILINEAR) @@ -226,7 +226,7 @@ script_dir = os.path.dirname(os.path.realpath(__file__)) parser = OptionParser() parser.add_option("--wordlist", help="A list of words (required)", metavar="WORDS.txt") - parser.add_option("--random", help="Use random charcters instead of a wordlist", action="store_true") + parser.add_option("--random", help="Use random characters instead of a wordlist", action="store_true") parser.add_option("--key", help="The passphrase set as $wgCaptchaSecret (required)", metavar="KEY") parser.add_option("--output", help="The directory to put the images in - $wgCaptchaDirectory (required)", metavar="DIR") parser.add_option("--font", help="The font to use (required)", metavar="FONT.ttf") diff -Nru mediawiki-1.31.10/extensions/Gadgets/composer.json mediawiki-1.31.12/extensions/Gadgets/composer.json --- mediawiki-1.31.10/extensions/Gadgets/composer.json 2020-09-24 21:51:26.593668200 +0000 +++ mediawiki-1.31.12/extensions/Gadgets/composer.json 2020-12-18 02:50:58.825521500 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/ImageMap/composer.json mediawiki-1.31.12/extensions/ImageMap/composer.json --- mediawiki-1.31.10/extensions/ImageMap/composer.json 2020-09-24 21:51:26.625668800 +0000 +++ mediawiki-1.31.12/extensions/ImageMap/composer.json 2020-12-18 02:50:58.861522000 +0000 @@ -16,7 +16,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/InputBox/composer.json mediawiki-1.31.12/extensions/InputBox/composer.json --- mediawiki-1.31.10/extensions/InputBox/composer.json 2020-09-24 21:51:26.645669000 +0000 +++ mediawiki-1.31.12/extensions/InputBox/composer.json 2020-12-18 02:50:58.885522100 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/Interwiki/composer.json mediawiki-1.31.12/extensions/Interwiki/composer.json --- mediawiki-1.31.10/extensions/Interwiki/composer.json 2020-09-24 21:51:26.677669500 +0000 +++ mediawiki-1.31.12/extensions/Interwiki/composer.json 2020-12-18 02:50:58.913522500 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/LocalisationUpdate/composer.json mediawiki-1.31.12/extensions/LocalisationUpdate/composer.json --- mediawiki-1.31.10/extensions/LocalisationUpdate/composer.json 2020-09-24 21:51:26.705669900 +0000 +++ mediawiki-1.31.12/extensions/LocalisationUpdate/composer.json 2020-12-18 02:50:58.949523000 +0000 @@ -33,7 +33,7 @@ }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/MultimediaViewer/composer.json mediawiki-1.31.12/extensions/MultimediaViewer/composer.json --- mediawiki-1.31.10/extensions/MultimediaViewer/composer.json 2020-09-24 21:51:26.733670200 +0000 +++ mediawiki-1.31.12/extensions/MultimediaViewer/composer.json 2020-12-18 02:50:58.977523300 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/Nuke/composer.json mediawiki-1.31.12/extensions/Nuke/composer.json --- mediawiki-1.31.10/extensions/Nuke/composer.json 2020-09-24 21:51:26.777670900 +0000 +++ mediawiki-1.31.12/extensions/Nuke/composer.json 2020-12-18 02:50:59.025523700 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/ParserFunctions/composer.json mediawiki-1.31.12/extensions/ParserFunctions/composer.json --- mediawiki-1.31.10/extensions/ParserFunctions/composer.json 2020-09-24 21:51:26.837671800 +0000 +++ mediawiki-1.31.12/extensions/ParserFunctions/composer.json 2020-12-18 02:50:59.105524800 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/ParserFunctions/includes/ExtParserFunctions.php mediawiki-1.31.12/extensions/ParserFunctions/includes/ExtParserFunctions.php --- mediawiki-1.31.10/extensions/ParserFunctions/includes/ExtParserFunctions.php 2020-09-24 21:51:26.841672000 +0000 +++ mediawiki-1.31.12/extensions/ParserFunctions/includes/ExtParserFunctions.php 2020-12-18 02:50:59.109524700 +0000 @@ -385,7 +385,7 @@ /** * @param Parser $parser - * @param PPFrame $frame + * @param PPFrame|null $frame * @param string $format * @param string $date * @param string $language diff -Nru mediawiki-1.31.10/extensions/PdfHandler/composer.json mediawiki-1.31.12/extensions/PdfHandler/composer.json --- mediawiki-1.31.10/extensions/PdfHandler/composer.json 2020-09-24 21:51:26.861672200 +0000 +++ mediawiki-1.31.12/extensions/PdfHandler/composer.json 2020-12-18 02:50:59.133525000 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/Poem/composer.json mediawiki-1.31.12/extensions/Poem/composer.json --- mediawiki-1.31.10/extensions/Poem/composer.json 2020-09-24 21:51:26.877672400 +0000 +++ mediawiki-1.31.12/extensions/Poem/composer.json 2020-12-18 02:50:59.157525300 +0000 @@ -5,7 +5,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/Renameuser/composer.json mediawiki-1.31.12/extensions/Renameuser/composer.json --- mediawiki-1.31.10/extensions/Renameuser/composer.json 2020-09-24 21:51:26.897672700 +0000 +++ mediawiki-1.31.12/extensions/Renameuser/composer.json 2020-12-18 02:50:59.185525700 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/ReplaceText/composer.json mediawiki-1.31.12/extensions/ReplaceText/composer.json --- mediawiki-1.31.10/extensions/ReplaceText/composer.json 2020-09-24 21:51:26.921673000 +0000 +++ mediawiki-1.31.12/extensions/ReplaceText/composer.json 2020-12-18 02:50:59.209525800 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/ReplaceText/src/ReplaceTextUtils.php mediawiki-1.31.12/extensions/ReplaceText/src/ReplaceTextUtils.php --- mediawiki-1.31.10/extensions/ReplaceText/src/ReplaceTextUtils.php 2020-09-24 21:51:26.921673000 +0000 +++ mediawiki-1.31.12/extensions/ReplaceText/src/ReplaceTextUtils.php 2020-12-18 02:50:59.213525800 +0000 @@ -25,7 +25,7 @@ /** * Shim for compatibility * @param Title $title to link to - * @param string $text to show + * @param string|null $text to show * @return string HTML for link */ public static function link( Title $title, $text = null ) { diff -Nru mediawiki-1.31.10/extensions/SpamBlacklist/composer.json mediawiki-1.31.12/extensions/SpamBlacklist/composer.json --- mediawiki-1.31.10/extensions/SpamBlacklist/composer.json 2020-09-24 21:51:26.945673500 +0000 +++ mediawiki-1.31.12/extensions/SpamBlacklist/composer.json 2020-12-18 02:50:59.237526200 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/SpamBlacklist/includes/SpamBlacklist.php mediawiki-1.31.12/extensions/SpamBlacklist/includes/SpamBlacklist.php --- mediawiki-1.31.10/extensions/SpamBlacklist/includes/SpamBlacklist.php 2020-09-24 21:51:26.945673500 +0000 +++ mediawiki-1.31.12/extensions/SpamBlacklist/includes/SpamBlacklist.php 2020-12-18 02:50:59.241526100 +0000 @@ -41,7 +41,7 @@ /** * @param string[] $links An array of links to check against the blacklist - * @param Title $title The title of the page to which the filter shall be applied. + * @param Title|null $title The title of the page to which the filter shall be applied. * This is used to load the old links already on the page, so * the filter is only applied to links that got added. If not given, * the filter is applied to all $links. diff -Nru mediawiki-1.31.10/extensions/SyntaxHighlight_GeSHi/composer.json mediawiki-1.31.12/extensions/SyntaxHighlight_GeSHi/composer.json --- mediawiki-1.31.10/extensions/SyntaxHighlight_GeSHi/composer.json 2020-09-24 21:51:26.973673800 +0000 +++ mediawiki-1.31.12/extensions/SyntaxHighlight_GeSHi/composer.json 2020-12-18 02:50:59.269526500 +0000 @@ -3,7 +3,7 @@ "description": "Syntax highlighting extension for MediaWiki", "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/extensions/TitleBlacklist/composer.json mediawiki-1.31.12/extensions/TitleBlacklist/composer.json --- mediawiki-1.31.10/extensions/TitleBlacklist/composer.json 2020-09-24 21:51:26.997674200 +0000 +++ mediawiki-1.31.12/extensions/TitleBlacklist/composer.json 2020-12-18 02:50:59.301526800 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" }, diff -Nru mediawiki-1.31.10/extensions/WikiEditor/composer.json mediawiki-1.31.12/extensions/WikiEditor/composer.json --- mediawiki-1.31.10/extensions/WikiEditor/composer.json 2020-09-24 21:51:27.021674400 +0000 +++ mediawiki-1.31.12/extensions/WikiEditor/composer.json 2020-12-18 02:50:59.333527300 +0000 @@ -2,7 +2,7 @@ "license": "GPL-2.0-or-later", "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1", "mediawiki/mediawiki-phan-config": "0.2.0" diff -Nru mediawiki-1.31.10/includes/Defines.php mediawiki-1.31.12/includes/Defines.php --- mediawiki-1.31.10/includes/Defines.php 2020-09-24 21:51:25.573653700 +0000 +++ mediawiki-1.31.12/includes/Defines.php 2020-12-18 02:50:09.360964500 +0000 @@ -37,7 +37,7 @@ * * @since 1.31.7 */ -define( 'MW_VERSION', '1.31.10' ); +define( 'MW_VERSION', '1.31.12' ); # Obsolete aliases /** diff -Nru mediawiki-1.31.10/includes/EditPage.php mediawiki-1.31.12/includes/EditPage.php --- mediawiki-1.31.10/includes/EditPage.php 2020-09-24 21:51:25.573653700 +0000 +++ mediawiki-1.31.12/includes/EditPage.php 2020-12-18 01:30:56.059494000 +0000 @@ -2513,7 +2513,15 @@ $user = User::newFromName( $username, false /* allow IP users */ ); $ip = User::isIP( $username ); $block = Block::newFromTarget( $user, $user ); - if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist + + $userExists = ( $user && $user->isLoggedIn() ); + if ( $userExists && $user->isHidden() && !$this->context->getUser()->isAllowed( 'hideuser' ) ) { + // If the user exists, but is hidden, and the viewer cannot see hidden + // users, pretend like they don't exist at all. See T120883 + $userExists = false; + } + + if ( !$userExists && !$ip ) { # User does not exist $out->wrapWikiMsg( "
\n$1\n
", [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] ); } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { diff -Nru mediawiki-1.31.10/includes/OutputPage.php mediawiki-1.31.12/includes/OutputPage.php --- mediawiki-1.31.10/includes/OutputPage.php 2020-09-24 21:51:25.581653600 +0000 +++ mediawiki-1.31.12/includes/OutputPage.php 2020-12-18 01:30:56.067494000 +0000 @@ -3162,7 +3162,9 @@ $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); } - if ( $relevantUser ) { + if ( $relevantUser && ( !$relevantUser->isHidden() || $user->isAllowed( 'hideuser' ) ) ) { + // T120883 if the user is hidden and the viewer cannot see + // hidden users, pretend like it does not exist at all. $vars['wgRelevantUserName'] = $relevantUser->getName(); } diff -Nru mediawiki-1.31.10/includes/libs/composer/ComposerJson.php mediawiki-1.31.12/includes/libs/composer/ComposerJson.php --- mediawiki-1.31.10/includes/libs/composer/ComposerJson.php 2020-09-24 21:51:25.693655300 +0000 +++ mediawiki-1.31.12/includes/libs/composer/ComposerJson.php 2020-12-18 01:30:56.203495500 +0000 @@ -24,7 +24,9 @@ $deps = []; if ( isset( $this->contents['require'] ) ) { foreach ( $this->contents['require'] as $package => $version ) { - if ( $package !== "php" && strpos( $package, 'ext-' ) !== 0 ) { + // Examples of package dependancies that don't have a / in the name: + // php, ext-xml, composer-plugin-api + if ( strpos( $package, '/' ) !== false ) { $deps[$package] = self::normalizeVersion( $version ); } } diff -Nru mediawiki-1.31.10/includes/libs/objectcache/MemcachedClient.php mediawiki-1.31.12/includes/libs/objectcache/MemcachedClient.php --- mediawiki-1.31.10/includes/libs/objectcache/MemcachedClient.php 2020-09-24 21:51:25.697655400 +0000 +++ mediawiki-1.31.12/includes/libs/objectcache/MemcachedClient.php 2020-12-18 01:30:56.211495600 +0000 @@ -544,11 +544,12 @@ continue; } $key = is_array( $key ) ? $key[1] : $key; - if ( !isset( $sock_keys[$sock] ) ) { - $sock_keys[intval( $sock )] = array(); + $sockValue = intval( $sock ); + if ( !isset( $sock_keys[$sockValue] ) ) { + $sock_keys[$sockValue] = array(); $socks[] = $sock; } - $sock_keys[intval( $sock )][] = $key; + $sock_keys[$sockValue][] = $key; } $gather = array(); diff -Nru mediawiki-1.31.10/includes/logging/BlockLogFormatter.php mediawiki-1.31.12/includes/logging/BlockLogFormatter.php --- mediawiki-1.31.10/includes/logging/BlockLogFormatter.php 2020-09-24 21:51:25.709655500 +0000 +++ mediawiki-1.31.12/includes/logging/BlockLogFormatter.php 2020-12-18 01:30:56.223495700 +0000 @@ -57,13 +57,14 @@ // The lrm is needed to make sure that the number // is shown on the correct side of the tooltip text. $durationTooltip = '‎' . htmlspecialchars( $params[4] ); + $blockExpiry = $this->context->getLanguage()->translateBlockExpiry( + $params[4], + $this->context->getUser(), + wfTimestamp( TS_UNIX, $this->entry->getTimestamp() ) + ); $params[4] = Message::rawParam( "" . - $this->context->getLanguage()->translateBlockExpiry( - $params[4], - $this->context->getUser(), - wfTimestamp( TS_UNIX, $this->entry->getTimestamp() ) - ) . + htmlspecialchars( $blockExpiry ) . '' ); $params[5] = isset( $params[5] ) ? diff -Nru mediawiki-1.31.10/includes/media/FormatMetadata.php mediawiki-1.31.12/includes/media/FormatMetadata.php --- mediawiki-1.31.10/includes/media/FormatMetadata.php 2020-09-24 21:51:25.709655500 +0000 +++ mediawiki-1.31.12/includes/media/FormatMetadata.php 2020-12-18 01:30:56.223495700 +0000 @@ -270,7 +270,12 @@ // TODO: YCbCrCoefficients #p27 (see annex E) case 'ExifVersion': - case 'FlashpixVersion': + // PHP likes to be the odd one out with casing of FlashPixVersion; + // https://www.exif.org/Exif2-2.PDF#page=32 and + // https://www.digitalgalen.net/Documents/External/XMP/XMPSpecificationPart2.pdf#page=51 + // both use FlashpixVersion. However, since at least 2002, PHP has used FlashPixVersion at + // https://github.com/php/php-src/blame/master/ext/exif/exif.c#L725 + case 'FlashPixVersion': $val = (int)$val / 100; break; diff -Nru mediawiki-1.31.10/includes/page/Article.php mediawiki-1.31.12/includes/page/Article.php --- mediawiki-1.31.10/includes/page/Article.php 2020-09-24 21:51:25.713655700 +0000 +++ mediawiki-1.31.12/includes/page/Article.php 2020-12-18 01:30:56.227496000 +0000 @@ -1164,6 +1164,14 @@ $ip = User::isIP( $rootPart ); $block = Block::newFromTarget( $user, $user ); + if ( $user && $user->isLoggedIn() && $user->isHidden() && + !$this->getContext()->getUser()->isAllowed( 'hideuser' ) + ) { + // T120883 if the user is hidden and the viewer cannot see hidden + // users, pretend like it does not exist at all. + $user = false; + } + if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $outputPage->wrapWikiMsg( "
\n\$1\n
", [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] ); diff -Nru mediawiki-1.31.10/includes/profiler/SectionProfiler.php mediawiki-1.31.12/includes/profiler/SectionProfiler.php --- mediawiki-1.31.10/includes/profiler/SectionProfiler.php 2020-09-24 21:51:25.721655600 +0000 +++ mediawiki-1.31.12/includes/profiler/SectionProfiler.php 2020-12-18 01:30:56.239496000 +0000 @@ -96,9 +96,15 @@ public function getFunctionStats() { $this->collateData(); - $totalCpu = max( $this->end['cpu'] - $this->start['cpu'], 0 ); - $totalReal = max( $this->end['real'] - $this->start['real'], 0 ); - $totalMem = max( $this->end['memory'] - $this->start['memory'], 0 ); + if ( is_array( $this->start ) ) { + $totalCpu = max( $this->end['cpu'] - $this->start['cpu'], 0 ); + $totalReal = max( $this->end['real'] - $this->start['real'], 0 ); + $totalMem = max( $this->end['memory'] - $this->start['memory'], 0 ); + } else { + $totalCpu = 0; + $totalReal = 0; + $totalMem = 0; + } $profile = []; foreach ( $this->collated as $fname => $data ) { diff -Nru mediawiki-1.31.10/includes/skins/BaseTemplate.php mediawiki-1.31.12/includes/skins/BaseTemplate.php --- mediawiki-1.31.10/includes/skins/BaseTemplate.php 2020-09-24 21:51:25.733656000 +0000 +++ mediawiki-1.31.12/includes/skins/BaseTemplate.php 2020-12-18 01:30:56.247496100 +0000 @@ -596,7 +596,7 @@ } } - if ( $option == 'flat' ) { + if ( $option == 'flat' && count( $validFooterLinks ) ) { // fold footerlinks into a single array using a bit of trickery $validFooterLinks = call_user_func_array( 'array_merge', diff -Nru mediawiki-1.31.10/includes/skins/SkinTemplate.php mediawiki-1.31.12/includes/skins/SkinTemplate.php --- mediawiki-1.31.10/includes/skins/SkinTemplate.php 2020-09-24 21:51:25.733656000 +0000 +++ mediawiki-1.31.12/includes/skins/SkinTemplate.php 2020-12-18 02:49:50.024746700 +0000 @@ -1349,6 +1349,18 @@ } $user = $this->getRelevantUser(); + + // The relevant user should only be set if it exists. However, if it exists but is hidden, + // and the viewer cannot see hidden users, this exposes the fact that the user exists; + // pretend like the user does not exist in such cases, by setting $user to null, which + // is what getRelevantUser returns if there is no user set (though it is documented as + // always returning a User...) See T120883 + if ( $user && $user->isRegistered() && $user->isHidden() && + !$this->getUser()->isAllowed( 'hideuser' ) + ) { + $user = null; + } + if ( $user ) { $rootUser = $user->getName(); diff -Nru mediawiki-1.31.10/includes/specials/SpecialBotPasswords.php mediawiki-1.31.12/includes/specials/SpecialBotPasswords.php --- mediawiki-1.31.10/includes/specials/SpecialBotPasswords.php 2020-09-24 21:51:25.737655900 +0000 +++ mediawiki-1.31.12/includes/specials/SpecialBotPasswords.php 2020-12-18 01:30:56.251496000 +0000 @@ -303,6 +303,11 @@ ) ] ); + if ( $bp === null ) { + // Messages: botpasswords-insert-failed, botpasswords-update-failed + return Status::newFatal( "botpasswords-{$this->operation}-failed", $this->par ); + } + if ( $this->operation === 'insert' || !empty( $data['resetPassword'] ) ) { $this->password = BotPassword::generatePassword( $this->getConfig() ); $passwordFactory = new PasswordFactory(); @@ -312,12 +317,7 @@ $password = null; } - if ( $bp->save( $this->operation, $password ) ) { - return Status::newGood(); - } else { - // Messages: botpasswords-insert-failed, botpasswords-update-failed - return Status::newFatal( "botpasswords-{$this->operation}-failed", $this->par ); - } + return $bp->save( $this->operation, $password ); } public function onSuccess() { diff -Nru mediawiki-1.31.10/includes/specials/SpecialContributions.php mediawiki-1.31.12/includes/specials/SpecialContributions.php --- mediawiki-1.31.10/includes/specials/SpecialContributions.php 2020-09-24 21:51:25.737655900 +0000 +++ mediawiki-1.31.12/includes/specials/SpecialContributions.php 2020-12-18 02:49:50.024746700 +0000 @@ -123,7 +123,15 @@ # For IP ranges, we want the contributionsSub, but not the skin-dependent # links under 'Tools', which may include irrelevant links like 'Logs'. - if ( !IP::isValidRange( $target ) ) { + if ( !IP::isValidRange( $target ) && + ( User::isIP( $target ) || $userObj->isLoggedIn() ) + ) { + // Don't add non-existent users, because hidden users + // that we add here will be removed later to pretend + // that they don't exist, and if users that actually don't + // exist are added here and then not removed, it exposes + // which users exist and are hidden vs. which actually don't + // exist. But, do set the relevant user for single IPs. $this->getSkin()->setRelevantUser( $userObj ); } } @@ -277,7 +285,10 @@ } elseif ( $userObj->isAnon() ) { // No message for non-existing users $message = ''; + } elseif ( $userObj->isHidden() && !$this->getUser()->isAllowed( 'hideuser' ) ) { + $message = ''; } else { + // Not hidden, or hidden but the viewer can still see it $message = 'sp-contributions-footer'; } @@ -301,7 +312,14 @@ * Could be combined. */ protected function contributionsSub( $userObj ) { - if ( $userObj->isAnon() ) { + $isAnon = $userObj->isAnon(); + if ( !$isAnon && $userObj->isHidden() && !$this->getUser->isAllowed( 'hideuser' ) ) { + // T120883 if the user is hidden and the viewer cannot see hidden + // users, pretend like it does not exist at all. + $isAnon = true; + } + + if ( $isAnon ) { // Show a warning message that the user being searched for doesn't exists. // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx', // but returns false for IP ranges. We don't want to suggest either of these are diff -Nru mediawiki-1.31.10/includes/specials/SpecialLog.php mediawiki-1.31.12/includes/specials/SpecialLog.php --- mediawiki-1.31.10/includes/specials/SpecialLog.php 2020-09-24 21:51:25.741656000 +0000 +++ mediawiki-1.31.12/includes/specials/SpecialLog.php 2020-12-18 01:30:56.255496000 +0000 @@ -275,6 +275,18 @@ $s .= Html::hidden( 'action', 'historysubmit' ) . "\n"; $s .= Html::hidden( 'type', 'logging' ) . "\n"; + // If no title is set, the fallback is to use the main page, as defined + // by MediaWiki:Mainpage + // On wikis where the main page can be translated, MediaWiki:Mainpage + // is sometimes set to use Special:MyLanguage to redirect to the + // appropriate version. This is interpreted as a special page, and + // Action::getActionName forces the action to be 'view' if the title + // cannot be used as a WikiPage, which includes all pages in NS_SPECIAL. + // Set a dummy title to avoid this. The title provided is unused + // by the SpecialPageAction class and does not matter. + // See T205908 + $s .= Html::hidden( 'title', 'Unused' ) . "\n"; + $buttons = ''; if ( $canRevDelete ) { $buttons .= Html::element( diff -Nru mediawiki-1.31.10/includes/specials/SpecialUserrights.php mediawiki-1.31.12/includes/specials/SpecialUserrights.php --- mediawiki-1.31.10/includes/specials/SpecialUserrights.php 2020-09-24 21:51:25.745656000 +0000 +++ mediawiki-1.31.12/includes/specials/SpecialUserrights.php 2020-12-18 01:30:56.263496200 +0000 @@ -883,10 +883,12 @@ $expiryFormatted = $uiLanguage->userTimeAndDate( $currentExpiry, $uiUser ); $expiryFormattedD = $uiLanguage->userDate( $currentExpiry, $uiUser ); $expiryFormattedT = $uiLanguage->userTime( $currentExpiry, $uiUser ); - $expiryHtml = $this->msg( 'userrights-expiry-current' )->params( - $expiryFormatted, $expiryFormattedD, $expiryFormattedT )->text(); + $expiryHtml = Xml::element( 'span', null, + $this->msg( 'userrights-expiry-current' )->params( + $expiryFormatted, $expiryFormattedD, $expiryFormattedT )->text() ); } else { - $expiryHtml = $this->msg( 'userrights-expiry-none' )->text(); + $expiryHtml = Xml::element( 'span', null, + $this->msg( 'userrights-expiry-none' )->text() ); } // T171345: Add a hidden form element so that other groups can still be manipulated, // otherwise saving errors out with an invalid expiry time for this group. diff -Nru mediawiki-1.31.10/includes/specials/helpers/LoginHelper.php mediawiki-1.31.12/includes/specials/helpers/LoginHelper.php --- mediawiki-1.31.10/includes/specials/helpers/LoginHelper.php 2020-09-24 21:51:25.745656000 +0000 +++ mediawiki-1.31.12/includes/specials/helpers/LoginHelper.php 2020-12-18 01:30:56.263496200 +0000 @@ -65,7 +65,7 @@ $type, $returnTo = '', $returnToQuery = '', $stickHTTPS = false ) { $config = $this->getConfig(); - if ( $type !== 'error' && $wgRedirectOnLogin !== null ) { + if ( $type !== 'error' && $config->get( 'RedirectOnLogin' ) !== null ) { $returnTo = $config->get( 'RedirectOnLogin' ); $returnToQuery = []; } elseif ( is_string( $returnToQuery ) ) { diff -Nru mediawiki-1.31.10/includes/user/BotPassword.php mediawiki-1.31.12/includes/user/BotPassword.php --- mediawiki-1.31.10/includes/user/BotPassword.php 2020-09-24 21:51:25.749656200 +0000 +++ mediawiki-1.31.12/includes/user/BotPassword.php 2020-12-18 01:30:56.267496300 +0000 @@ -30,6 +30,18 @@ const APPID_MAXLENGTH = 32; + /** + * Maximum length of the json representation of restrictions + * @since 1.31.11 + */ + const RESTRICTIONS_MAXLENGTH = 65535; + + /** + * Maximum length of the json representation of grants + * @since 1.31.11 + */ + const GRANTS_MAXLENGTH = 65535; + /** @var bool */ private $isSaved; @@ -274,17 +286,44 @@ * Save the BotPassword to the database * @param string $operation 'update' or 'insert' * @param Password|null $password Password to set. - * @return bool Success + * @return Status + * @throws UnexpectedValueException */ public function save( $operation, Password $password = null ) { + // Ensure operation is valid + if ( $operation !== 'insert' && $operation !== 'update' ) { + throw new UnexpectedValueException( + "Expected 'insert' or 'update'; got '{$operation}'." + ); + } + $conds = [ 'bp_user' => $this->centralId, 'bp_app_id' => $this->appId, ]; + + $res = Status::newGood(); + + $restrictions = $this->restrictions->toJson(); + + if ( strlen( $restrictions ) > self::RESTRICTIONS_MAXLENGTH ) { + $res->fatal( 'botpasswords-toolong-restrictions' ); + } + + $grants = FormatJson::encode( $this->grants ); + + if ( strlen( $grants ) > self::GRANTS_MAXLENGTH ) { + $res->fatal( 'botpasswords-toolong-grants' ); + } + + if ( !$res->isGood() ) { + return $res; + } + $fields = [ 'bp_token' => MWCryptRand::generateHex( User::TOKEN_LENGTH ), - 'bp_restrictions' => $this->restrictions->toJson(), - 'bp_grants' => FormatJson::encode( $this->grants ), + 'bp_restrictions' => $restrictions, + 'bp_grants' => $grants, ]; if ( $password !== null ) { @@ -294,24 +333,24 @@ } $dbw = self::getDB( DB_MASTER ); - switch ( $operation ) { - case 'insert': - $dbw->insert( 'bot_passwords', $fields + $conds, __METHOD__, [ 'IGNORE' ] ); - break; - - case 'update': - $dbw->update( 'bot_passwords', $fields, $conds, __METHOD__ ); - break; - default: - return false; + if ( $operation === 'insert' ) { + $dbw->insert( 'bot_passwords', $fields + $conds, __METHOD__, [ 'IGNORE' ] ); + } else { + // Must be update, already checked above + $dbw->update( 'bot_passwords', $fields, $conds, __METHOD__ ); } + $ok = (bool)$dbw->affectedRows(); if ( $ok ) { $this->token = $dbw->selectField( 'bot_passwords', 'bp_token', $conds, __METHOD__ ); $this->isSaved = true; + + return $res; } - return $ok; + + // Messages: botpasswords-insert-failed, botpasswords-update-failed + return Status::newFatal( "botpasswords-{$operation}-failed", $this->appId ); } /** diff -Nru mediawiki-1.31.10/includes/user/CentralIdLookup.php mediawiki-1.31.12/includes/user/CentralIdLookup.php --- mediawiki-1.31.10/includes/user/CentralIdLookup.php 2020-09-24 21:51:25.749656200 +0000 +++ mediawiki-1.31.12/includes/user/CentralIdLookup.php 2020-12-18 01:30:56.267496300 +0000 @@ -79,7 +79,7 @@ * * @return CentralIdLookup|null */ - public static function factoryNonLocal(): self { + public static function factoryNonLocal() { $centralIdLookup = self::factory(); if ( $centralIdLookup instanceof LocalIdLookup ) { diff -Nru mediawiki-1.31.10/includes/user/User.php mediawiki-1.31.12/includes/user/User.php --- mediawiki-1.31.10/includes/user/User.php 2020-09-24 21:51:25.749656200 +0000 +++ mediawiki-1.31.12/includes/user/User.php 2020-12-18 02:50:09.360964500 +0000 @@ -3772,11 +3772,25 @@ } /** - * Get whether the user is logged in + * Alias of isLoggedIn() with a name that describes its actual functionality. + * + * @return bool True if user is registered on this wiki, i.e., has a user ID. False if user is + * anonymous or has no local account (which can happen when importing). This is equivalent to + * getId() != 0 and is provided for code readability. + * @since 1.31.12 + */ + public function isRegistered() { + return $this->getId() != 0; + } + + /** + * Get whether the user is registered. + * + * @deprecated since 1.31.12; use isRegistered() directly * @return bool */ public function isLoggedIn() { - return $this->getId() != 0; + return $this->isRegistered(); } /** diff -Nru mediawiki-1.31.10/languages/Language.php mediawiki-1.31.12/languages/Language.php --- mediawiki-1.31.10/languages/Language.php 2020-09-24 21:51:25.757656300 +0000 +++ mediawiki-1.31.12/languages/Language.php 2020-12-18 01:30:56.271496300 +0000 @@ -4097,14 +4097,14 @@ $duration = SpecialBlock::getSuggestedDurations( $this ); foreach ( $duration as $show => $value ) { if ( strcmp( $str, $value ) == 0 ) { - return htmlspecialchars( trim( $show ) ); + return trim( $show ); } } if ( wfIsInfinity( $str ) ) { foreach ( $duration as $show => $value ) { if ( wfIsInfinity( $value ) ) { - return htmlspecialchars( trim( $show ) ); + return trim( $show ); } } } diff -Nru mediawiki-1.31.10/languages/classes/LanguageFi.php mediawiki-1.31.12/languages/classes/LanguageFi.php --- mediawiki-1.31.10/languages/classes/LanguageFi.php 2020-09-24 21:51:25.757656300 +0000 +++ mediawiki-1.31.12/languages/classes/LanguageFi.php 2020-12-18 01:30:56.275496200 +0000 @@ -166,6 +166,6 @@ $final .= ' ' . $item; } - return htmlspecialchars( trim( $final ) ); + return trim( $final ); } } diff -Nru mediawiki-1.31.10/languages/i18n/en.json mediawiki-1.31.12/languages/i18n/en.json --- mediawiki-1.31.10/languages/i18n/en.json 2020-09-24 21:51:25.845657600 +0000 +++ mediawiki-1.31.12/languages/i18n/en.json 2020-12-18 01:30:56.379497500 +0000 @@ -550,6 +550,8 @@ "botpasswords-help-grants": "Grants allow access to rights already held by your user account. Enabling a grant here does not provide access to any rights that your user account would not otherwise have. See the [[Special:ListGrants|table of grants]] for more information.", "botpasswords-label-grants-column": "Granted", "botpasswords-bad-appid": "The bot name \"$1\" is not valid.", + "botpasswords-toolong-restrictions": "There are too many IP addresses or ranges entered.", + "botpasswords-toolong-grants": "There are too many grants selected.", "botpasswords-insert-failed": "Failed to add bot name \"$1\". Was it already added?", "botpasswords-update-failed": "Failed to update bot name \"$1\". Was it deleted?", "botpasswords-created-title": "Bot password created", diff -Nru mediawiki-1.31.10/languages/i18n/qqq.json mediawiki-1.31.12/languages/i18n/qqq.json --- mediawiki-1.31.10/languages/i18n/qqq.json 2020-09-24 21:51:26.025660000 +0000 +++ mediawiki-1.31.12/languages/i18n/qqq.json 2020-12-18 01:30:56.567499600 +0000 @@ -747,6 +747,8 @@ "botpasswords-help-grants": "Help text for the grant selection checkmatrix.\n\nIdentical:\n* {{msg-mw|Mwoauth-consumer-grantshelp}}", "botpasswords-label-grants-column": "Label for the checkbox column on the checkmatrix for selecting grants allowed when the bot password is used.", "botpasswords-bad-appid": "Used as an error message when an invalid \"bot name\" is supplied on [[Special:BotPasswords]]. Parameters:\n* $1 - The rejected bot name.", + "botpasswords-toolong-restrictions": "Used when the bot password allowed IP ranges are too long when turned into JSON.", + "botpasswords-toolong-grants": "Error message when the selected grants are too long (usually means too many selected) when turned into JSON.", "botpasswords-insert-failed": "Error message when saving a new bot password failed. It's likely that the failure was because the user resubmitted the form after a previous successful save. Parameters:\n* $1 - Bot name", "botpasswords-update-failed": "Error message when saving changes to an existing bot password failed. It's likely that the failure was because the user deleted the bot password in another browser window. Parameters:\n* $1 - Bot name", "botpasswords-created-title": "Title of the success page when a new bot password is created.", diff -Nru mediawiki-1.31.10/skins/MonoBook/composer.json mediawiki-1.31.12/skins/MonoBook/composer.json --- mediawiki-1.31.10/skins/MonoBook/composer.json 2020-09-24 21:51:27.065675000 +0000 +++ mediawiki-1.31.12/skins/MonoBook/composer.json 2020-12-18 02:50:59.389527800 +0000 @@ -2,7 +2,7 @@ "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", "jakub-onderka/php-console-highlighter": "0.3.2", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "mediawiki/minus-x": "0.3.1" }, "scripts": { diff -Nru mediawiki-1.31.10/skins/Timeless/composer.json mediawiki-1.31.12/skins/Timeless/composer.json --- mediawiki-1.31.10/skins/Timeless/composer.json 2020-09-24 21:51:27.093675600 +0000 +++ mediawiki-1.31.12/skins/Timeless/composer.json 2020-12-18 02:50:59.421528300 +0000 @@ -1,7 +1,7 @@ { "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1" }, diff -Nru mediawiki-1.31.10/skins/Vector/composer.json mediawiki-1.31.12/skins/Vector/composer.json --- mediawiki-1.31.10/skins/Vector/composer.json 2020-09-24 21:51:27.129676000 +0000 +++ mediawiki-1.31.12/skins/Vector/composer.json 2020-12-18 02:50:59.453528600 +0000 @@ -35,7 +35,7 @@ }, "require-dev": { "jakub-onderka/php-parallel-lint": "1.0.0", - "mediawiki/mediawiki-codesniffer": "18.0.0", + "mediawiki/mediawiki-codesniffer": "19.1.0", "jakub-onderka/php-console-highlighter": "0.3.2", "mediawiki/minus-x": "0.3.1" }, diff -Nru mediawiki-1.31.10/tests/phan/bin/postprocess-phan.php mediawiki-1.31.12/tests/phan/bin/postprocess-phan.php --- mediawiki-1.31.10/tests/phan/bin/postprocess-phan.php 2020-09-24 21:51:26.313664200 +0000 +++ mediawiki-1.31.12/tests/phan/bin/postprocess-phan.php 2020-12-18 01:30:56.879503300 +0000 @@ -66,7 +66,10 @@ */ public function suppress( $input ) { $dom = new DOMDocument(); + + $oldDisable = libxml_disable_entity_loader( true ); $dom->loadXML( $input ); + $hasErrors = false; // DOMNodeList's are "live", convert to an array so it works as expected $files = []; @@ -95,6 +98,7 @@ } } echo $dom->saveXML(); + libxml_disable_entity_loader( $oldDisable ); return $hasErrors; } diff -Nru mediawiki-1.31.10/tests/phpunit/includes/user/BotPasswordTest.php mediawiki-1.31.12/tests/phpunit/includes/user/BotPasswordTest.php --- mediawiki-1.31.10/tests/phpunit/includes/user/BotPasswordTest.php 2020-09-24 21:51:26.345664700 +0000 +++ mediawiki-1.31.12/tests/phpunit/includes/user/BotPasswordTest.php 2020-12-18 01:30:56.919503700 +0000 @@ -365,8 +365,9 @@ ); $passwordHash = $password ? $passwordFactory->newFromPlaintext( $password ) : null; - $this->assertFalse( $bp->save( 'update', $passwordHash ) ); - $this->assertTrue( $bp->save( 'insert', $passwordHash ) ); + $this->assertFalse( $bp->save( 'update', $passwordHash )->isGood() ); + $this->assertTrue( $bp->save( 'insert', $passwordHash )->isGood() ); + $bp2 = BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST ); $this->assertInstanceOf( BotPassword::class, $bp2 ); $this->assertEquals( $bp->getUserCentralId(), $bp2->getUserCentralId() ); @@ -374,6 +375,8 @@ $this->assertEquals( $bp->getToken(), $bp2->getToken() ); $this->assertEquals( $bp->getRestrictions(), $bp2->getRestrictions() ); $this->assertEquals( $bp->getGrants(), $bp2->getGrants() ); + + /** @var Password $pw */ $pw = TestingAccessWrapper::newFromObject( $bp )->getPassword(); if ( $password === null ) { $this->assertInstanceOf( InvalidPassword::class, $pw ); @@ -384,9 +387,10 @@ $token = $bp->getToken(); $this->assertEquals( 42, $bp->getUserCentralId() ); $this->assertEquals( 'TestSave', $bp->getAppId() ); - $this->assertFalse( $bp->save( 'insert' ) ); - $this->assertTrue( $bp->save( 'update' ) ); + $this->assertFalse( $bp->save( 'insert' )->isGood() ); + $this->assertTrue( $bp->save( 'update' )->isGood() ); $this->assertNotEquals( $token, $bp->getToken() ); + $bp2 = BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST ); $this->assertInstanceOf( BotPassword::class, $bp2 ); $this->assertEquals( $bp->getToken(), $bp2->getToken() ); @@ -399,8 +403,10 @@ $passwordHash = $passwordFactory->newFromPlaintext( 'XXX' ); $token = $bp->getToken(); - $this->assertTrue( $bp->save( 'update', $passwordHash ) ); + $this->assertTrue( $bp->save( 'update', $passwordHash )->isGood() ); $this->assertNotEquals( $token, $bp->getToken() ); + + /** @var Password $pw */ $pw = TestingAccessWrapper::newFromObject( $bp )->getPassword(); $this->assertTrue( $pw->equals( 'XXX' ) ); @@ -408,7 +414,8 @@ $this->assertFalse( $bp->isSaved() ); $this->assertNull( BotPassword::newFromCentralId( 42, 'TestSave', BotPassword::READ_LATEST ) ); - $this->assertFalse( $bp->save( 'foobar' ) ); + $this->expectException( UnexpectedValueException::class ); + $bp->save( 'foobar' )->isGood(); } public static function provideSave() { @@ -417,4 +424,47 @@ [ 'foobar' ], ]; } + + /** + * Tests for error handling when bp_restrictions and bp_grants are too long + */ + public function testSaveValidation() { + $lotsOfIPs = [ + 'IPAddresses' => array_fill( + 0, + 5000, + "127.0.0.0/8" + ) + ]; + + $bp = BotPassword::newUnsaved( [ + 'centralId' => 42, + 'appId' => 'TestSave', + // When this becomes JSON, it'll be 70,017 characters, which is + // greater than BotPassword::GRANTS_MAXLENGTH, so it will cause an error. + 'restrictions' => MWRestrictions::newFromArray( $lotsOfIPs ), + 'grants' => [ + // Maximum length of the JSON is BotPassword::RESTRICTIONS_MAXLENGTH characters. + // So one long grant name should be good. Turning it into JSON will add + // a couple of extra characters, taking it over BotPassword::RESTRICTIONS_MAXLENGTH + // characters long, so it will cause an error. + str_repeat( '*', BotPassword::RESTRICTIONS_MAXLENGTH ) + ], + ] ); + + $status = $bp->save( 'insert' ); + + $this->assertFalse( $status->isGood() ); + $this->assertNotEmpty( $status->getErrors() ); + + $this->assertSame( + 'botpasswords-toolong-restrictions', + $status->getErrors()[0]['message'] + ); + + $this->assertSame( + 'botpasswords-toolong-grants', + $status->getErrors()[1]['message'] + ); + } }