Provide detailed information about invalid titles
authorBartosz Dziewoński <matma.rex@gmail.com>
Sat, 25 Apr 2015 22:43:37 +0000 (00:43 +0200)
committerBartosz Dziewoński <matma.rex@gmail.com>
Thu, 30 Apr 2015 21:50:07 +0000 (23:50 +0200)
includes/Title.php
* The private method Title::secureAndSplit() now throws a
  MalformedTitleException instead of returning false on invalid
  titles.
* Added Title::newFromTextThrow(), which behaves exactly like
  Title::newFromText() but throws MalformedTitleException instead of
  returning null on invalid titles.

includes/title/MediaWikiTitleCodec.php
* Provide more information with the thrown MalformedTitleExceptions.

includes/MediaWiki.php
* Use the new Title::newFromTextThrow() to get detailed error
  information, display it.

Change-Id: I4da8ecb457a77473e32d745ba48ab8505b35e45f

includes/MediaWiki.php
includes/Title.php
includes/exception/BadTitleError.php
includes/title/MalformedTitleException.php
includes/title/MediaWikiTitleCodec.php
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/TitleTest.php

index ec2f40f..4c44121 100644 (file)
@@ -51,6 +51,7 @@ class MediaWiki {
        /**
         * Parse the request to get the Title object
         *
+        * @throws MalformedTitleException If a title has been provided by the user, but is invalid.
         * @return Title Title object to be $wgTitle
         */
        private function parseTitle() {
@@ -110,7 +111,10 @@ class MediaWiki {
                }
 
                if ( $ret === null || ( $ret->getDBkey() == '' && !$ret->isExternal() ) ) {
-                       $ret = SpecialPage::getTitleFor( 'Badtitle' );
+                       // If we get here, we definitely don't have a valid title; throw an exception.
+                       // Try to get detailed invalid title exception first, fall back to MalformedTitleException.
+                       Title::newFromTextThrow( $title );
+                       throw new MalformedTitleException( null, $title );
                }
 
                return $ret;
@@ -122,7 +126,11 @@ class MediaWiki {
         */
        public function getTitle() {
                if ( !$this->context->hasTitle() ) {
-                       $this->context->setTitle( $this->parseTitle() );
+                       try {
+                               $this->context->setTitle( $this->parseTitle() );
+                       } catch ( MalformedTitleException $ex ) {
+                               $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
+                       }
                }
                return $this->context->getTitle();
        }
@@ -174,6 +182,11 @@ class MediaWiki {
                        || $title->isSpecial( 'Badtitle' )
                ) {
                        $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
+                       try {
+                               $this->parseTitle();
+                       } catch ( MalformedTitleException $ex ) {
+                               throw new BadTitleError( $ex );
+                       }
                        throw new BadTitleError();
                }
 
@@ -219,6 +232,11 @@ class MediaWiki {
                                $output->redirect( $url, 301 );
                        } else {
                                $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
+                               try {
+                                       $this->parseTitle();
+                               } catch ( MalformedTitleException $ex ) {
+                                       throw new BadTitleError( $ex );
+                               }
                                throw new BadTitleError();
                        }
                // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
index b0df15f..26a1b4f 100644 (file)
@@ -225,9 +225,11 @@ class Title {
        public static function newFromDBkey( $key ) {
                $t = new Title();
                $t->mDbkeyform = $key;
-               if ( $t->secureAndSplit() ) {
+
+               try {
+                       $t->secureAndSplit();
                        return $t;
-               } else {
+               } catch ( MalformedTitleException $ex ) {
                        return null;
                }
        }
@@ -266,6 +268,32 @@ class Title {
                        wfWarn( __METHOD__ . ': $text must be a string. This will throw an InvalidArgumentException in future.', 2 );
                }
 
+               try {
+                       return Title::newFromTextThrow( $text, $defaultNamespace );
+               } catch ( MalformedTitleException $ex ) {
+                       return null;
+               }
+       }
+
+       /**
+        * Like Title::newFromText(), but throws MalformedTitleException when the title is invalid,
+        * rather than returning null.
+        *
+        * The exception subclasses encode detailed information about why the title is invalid.
+        *
+        * @see Title::newFromText
+        *
+        * @since 1.25
+        * @param string $text Title text to check
+        * @param int $defaultNamespace
+        * @throws MalformedTitleException If the title is invalid
+        * @return Title
+        */
+       public static function newFromTextThrow( $text, $defaultNamespace = NS_MAIN ) {
+               if ( is_object( $text ) ) {
+                       throw new MWException( 'Title::newFromTextThrow given an object' );
+               }
+
                $cache = self::getTitleCache();
 
                /**
@@ -287,14 +315,11 @@ class Title {
                $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
                $t->mDefaultNamespace = intval( $defaultNamespace );
 
-               if ( $t->secureAndSplit() ) {
-                       if ( $defaultNamespace == NS_MAIN ) {
-                               $cache->set( $text, $t );
-                       }
-                       return $t;
-               } else {
-                       return null;
+               $t->secureAndSplit();
+               if ( $defaultNamespace == NS_MAIN ) {
+                       $cache->set( $text, $t );
                }
+               return $t;
        }
 
        /**
@@ -323,9 +348,11 @@ class Title {
                }
 
                $t->mDbkeyform = str_replace( ' ', '_', $url );
-               if ( $t->secureAndSplit() ) {
+
+               try {
+                       $t->secureAndSplit();
                        return $t;
-               } else {
+               } catch ( MalformedTitleException $ex ) {
                        return null;
                }
        }
@@ -507,9 +534,11 @@ class Title {
 
                $t = new Title();
                $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki, true );
-               if ( $t->secureAndSplit() ) {
+
+               try {
+                       $t->secureAndSplit();
                        return $t;
-               } else {
+               } catch ( MalformedTitleException $ex ) {
                        return null;
                }
        }
@@ -3310,6 +3339,7 @@ class Title {
         * namespace prefixes, sets the other forms, and canonicalizes
         * everything.
         *
+        * @throws MalformedTitleException On invalid titles
         * @return bool True on success
         */
        private function secureAndSplit() {
@@ -3320,15 +3350,12 @@ class Title {
 
                $dbkey = $this->mDbkeyform;
 
-               try {
-                       // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
-                       //        the parsing code with Title, while avoiding massive refactoring.
-                       // @todo: get rid of secureAndSplit, refactor parsing code.
-                       $titleParser = self::getTitleParser();
-                       $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
-               } catch ( MalformedTitleException $ex ) {
-                       return false;
-               }
+               // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
+               //        the parsing code with Title, while avoiding massive refactoring.
+               // @todo: get rid of secureAndSplit, refactor parsing code.
+               $titleParser = self::getTitleParser();
+               // MalformedTitleException can be thrown here
+               $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
 
                # Fill fields
                $this->setFragment( '#' . $parts['fragment'] );
index e62f8bd..4710022 100644 (file)
  */
 class BadTitleError extends ErrorPageError {
        /**
-        * @param string|Message $msg A message key (default: 'badtitletext')
+        * @param string|Message|MalformedTitleException $msg A message key (default: 'badtitletext'), or
+        *     a MalformedTitleException to figure out things from
         * @param array $params Parameter to wfMessage()
         */
        public function __construct( $msg = 'badtitletext', $params = array() ) {
-               parent::__construct( 'badtitle', $msg, $params );
+               if ( $msg instanceof MalformedTitleException ) {
+                       $errorMessage = $msg->getErrorMessage();
+                       if ( !$errorMessage ) {
+                               parent::__construct( 'badtitle', 'badtitletext', array() );
+                       } else {
+                               $errorMessageParams = $msg->getErrorMessageParameters();
+                               $titleText = $msg->getTitleText();
+                               if ( $titleText ) {
+                                       $errorMessageParams[] = $titleText;
+                               }
+                               parent::__construct( 'badtitle', $errorMessage, $errorMessageParams );
+                       }
+               } else {
+                       parent::__construct( 'badtitle', $msg, $params );
+               }
        }
 
        /**
@@ -47,5 +62,4 @@ class BadTitleError extends ErrorPageError {
                $wgOut->setStatusCode( 400 );
                parent::report();
        }
-
 }
index a9e58b3..e747778 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * Representation of a page title within %MediaWiki.
- *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * http://www.gnu.org/copyleft/gpl.html
  *
  * @file
- * @license GPL 2+
- * @author Daniel Kinzler
  */
 
 /**
  * MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
- *
- * @license GPL 2+
- * @author Daniel Kinzler
  * @since 1.23
  */
 class MalformedTitleException extends Exception {
+       private $titleText = null;
+       private $errorMessage = null;
+       private $errorMessageParameters = array();
+
+       /**
+        * @param string $errorMessage Localisation message describing the error (since MW 1.26)
+        * @param string $titleText The invalid title text (since MW 1.26)
+        * @param string[] $errorMessageParameters Additional parameters for the error message (since MW 1.26)
+        */
+       public function __construct( $errorMessage = null, $titleText = null, $errorMessageParameters = array() ) {
+               $this->errorMessage = $errorMessage;
+               $this->titleText = $titleText;
+               $this->errorMessageParameters = $errorMessageParameters;
+       }
+
+       /**
+        * @since 1.26
+        * @return string|null
+        */
+       public function getTitleText() {
+               return $this->titleText;
+       }
+       
+       /**
+        * @since 1.26
+        * @return string|null
+        */
+       public function getErrorMessage() {
+               return $this->errorMessage;
+       }
+       
+       /**
+        * @since 1.26
+        * @return string[]
+        */
+       public function getErrorMessageParameters() {
+               return $this->errorMessageParameters;
+       }
 }
index 20034b7..98cec59 100644 (file)
@@ -137,12 +137,12 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
 
                // Interwiki links are not supported by TitleValue
                if ( $parts['interwiki'] !== '' ) {
-                       throw new MalformedTitleException( 'Title must not contain an interwiki prefix: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-interwiki', $text );
                }
 
                // Relative fragment links are not supported by TitleValue
                if ( $parts['dbkey'] === '' ) {
-                       throw new MalformedTitleException( 'Title must not be empty: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-empty', $text );
                }
 
                return new TitleValue( $parts['namespace'], $parts['dbkey'], $parts['fragment'] );
@@ -232,7 +232,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
 
                if ( strpos( $dbkey, UtfNormal\Constants::UTF8_REPLACEMENT ) !== false ) {
                        # Contained illegal UTF-8 sequences or forbidden Unicode chars.
-                       throw new MalformedTitleException( 'Bad UTF-8 sequences found in title: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-utf8', $text );
                }
 
                $parts['dbkey'] = $dbkey;
@@ -246,7 +246,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                }
 
                if ( $dbkey == '' ) {
-                       throw new MalformedTitleException( 'Empty title: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-empty', $text );
                }
 
                # Namespace or interwiki prefix
@@ -263,11 +263,11 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                                        if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
                                                if ( $this->language->getNsIndex( $x[1] ) ) {
                                                        # Disallow Talk:File:x type titles...
-                                                       throw new MalformedTitleException( 'Bad namespace prefix: ' . $text );
+                                                       throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
                                                } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
                                                        //TODO: get rid of global state!
                                                        # Disallow Talk:Interwiki:x type titles...
-                                                       throw new MalformedTitleException( 'Interwiki prefix found in title: ' . $text );
+                                                       throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
                                                }
                                        }
                                } elseif ( Interwiki::isValidInterwiki( $p ) ) {
@@ -324,8 +324,9 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
 
                # Reject illegal characters.
                $rxTc = self::getTitleInvalidRegex();
-               if ( preg_match( $rxTc, $dbkey ) ) {
-                       throw new MalformedTitleException( 'Illegal characters found in title: ' . $text );
+               $matches = array();
+               if ( preg_match( $rxTc, $dbkey, $matches ) ) {
+                       throw new MalformedTitleException( 'title-invalid-characters', $text, array( $matches[0] ) );
                }
 
                # Pages with "/./" or "/../" appearing in the URLs will often be un-
@@ -343,23 +344,21 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                                substr( $dbkey, -3 ) == '/..'
                        )
                ) {
-                       throw new MalformedTitleException( 'Bad title: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-relative', $text );
                }
 
                # Magic tilde sequences? Nu-uh!
                if ( strpos( $dbkey, '~~~' ) !== false ) {
-                       throw new MalformedTitleException( 'Bad title: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-magic-tilde', $text );
                }
 
                # Limit the size of titles to 255 bytes. This is typically the size of the
                # underlying database field. We make an exception for special pages, which
                # don't need to be stored in the database, and may edge over 255 bytes due
                # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
-               if (
-                       ( $parts['namespace'] != NS_SPECIAL && strlen( $dbkey ) > 255 )
-                       || strlen( $dbkey ) > 512
-               ) {
-                       throw new MalformedTitleException( 'Title too long: ' . substr( $dbkey, 0, 255 ) . '...' );
+               $maxLength = ( $parts['namespace'] != NS_SPECIAL ) ? 255 : 512;
+               if ( strlen( $dbkey ) > $maxLength ) {
+                       throw new MalformedTitleException( 'title-invalid-too-long', $text, array( $maxLength ) );
                }
 
                # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
@@ -374,7 +373,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                # self-links with a fragment identifier.
                if ( $dbkey == '' && $parts['interwiki'] === '' ) {
                        if ( $parts['namespace'] != NS_MAIN ) {
-                               throw new MalformedTitleException( 'Empty title: ' . $text );
+                               throw new MalformedTitleException( 'title-invalid-empty', $text );
                        }
                }
 
@@ -390,7 +389,7 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
 
                // Any remaining initial :s are illegal.
                if ( $dbkey !== '' && ':' == $dbkey[0] ) {
-                       throw new MalformedTitleException( 'Title must not start with a colon: ' . $text );
+                       throw new MalformedTitleException( 'title-invalid-leading-colon', $text );
                }
 
                # Fill fields
index 8afed1a..d8554b2 100644 (file)
        "no-null-revision": "Could not create new null revision for page \"$1\"",
        "badtitle": "Bad title",
        "badtitletext": "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title.\nIt may contain one or more characters that cannot be used in titles.",
+       "title-invalid-empty": "The requested page title is empty or contains only the name of a namespace.",
+       "title-invalid-utf8": "The requested page title contains an invalid UTF-8 sequence.",
+       "title-invalid-interwiki": "Title contains an interwiki link",
+       "title-invalid-talk-namespace": "The requested page title refers to a talk page that can not exist.",
+       "title-invalid-characters": "The requested page title contains invalid characters: \"$1\".",
+       "title-invalid-relative": "Title has relative path. Relative page titles (./, ../) are invalid, because they will often be unreachable when handled by user\"s browser.",
+       "title-invalid-magic-tilde": "The requested page title contains invalid magic tilde sequence (<nowiki>~~~</nowiki>).",
+       "title-invalid-too-long": "The requested page title is too long. It must be no longer than $1 bytes in UTF-8 encoding.",
+       "title-invalid-leading-colon": "The requested page title contains an invalid colon at the beginning.",
        "perfcached": "The following data is cached and may not be up to date. A maximum of {{PLURAL:$1|one result is|$1 results are}} available in the cache.",
        "perfcachedts": "The following data is cached, and was last updated $1. A maximum of {{PLURAL:$4|one result is|$4 results are}} available in the cache.",
        "querypage-no-updates": "Updates for this page are currently disabled.\nData here will not presently be refreshed.",
index a7b5524..da9a8ac 100644 (file)
        "delete-hook-aborted": "Error message shown when an extension hook prevents a page deletion, but does not provide an error message.",
        "no-null-revision": "Error message shown when no null revision could be created to reflect a protection level change.\n\nAbout \"null revision\":\n* Create a new null-revision for insertion into a page's history. This will not re-save the text, but simply refer to the text from the previous version.\n* Such revisions can for instance identify page rename operations and other such meta-modifications.\n\nParameters:\n* $1 - page title",
        "badtitle": "The page title when a user requested a page with invalid page name. The content will be {{msg-mw|badtitletext}}.",
+       "title-invalid-empty": "Used as text of error message: empty title",
+       "title-invalid-utf8": "Used as text of error message: invalid UTF8 sequence",
+       "title-invalid-interwiki": "Used as text of error message: invalid interwiki link",
+       "title-invalid-talk-namespace": "Used as text of error message: invalid talk page",
+       "title-invalid-characters": "Used as text of error message: invalid characters in title ($1 is the character)",
+       "title-invalid-relative": "Used as text of error message: relative titles are invalid",
+       "title-invalid-magic-tilde": "Used as text of error message: magic tilde sequence is invalid in page title",
+       "title-invalid-too-long": "Used as text of error message: too long title ($1 is maximum length)",
+       "title-invalid-leading-colon": "Used as text of error message: colon at the beginning of title is invalid",
        "badtitletext": "The message shown when a user requested a page with invalid page name. The page title will be {{msg-mw|badtitle}}.\n\nSee also:\n* {{msg-mw|selfmove}}\n* {{msg-mw|immobile-source-namespace}}\n* {{msg-mw|immobile-target-namespace-iw}}\n* {{msg-mw|immobile-target-namespace}}",
        "perfcached": "Like {{msg-mw|perfcachedts}} but used when we do not know how long ago page was cached (unlikely to happen).\n\nParameters:\n* $1 - the max result cut off ($wgQueryCacheLimit)",
        "perfcachedts": "Used on pages that list page lists for which the displayed data is cached. Parameters:\n* $1 - a time stamp (date and time combined)\n* $2 - a date (optional)\n* $3 - a time (optional)\n* $4 - the cut off limit for cached results ($wgQueryCacheLimit). If there are more then this many results for the query, only the first $4 of those will be listed on the page. Usually $4 is about 1000.",
index 00c29ee..74a741a 100644 (file)
@@ -80,22 +80,22 @@ class TitleTest extends MediaWikiTestCase {
 
        public static function provideInvalidSecureAndSplit() {
                return array(
-                       array( '' ),
-                       array( ':' ),
-                       array( '__  __' ),
-                       array( '  __  ' ),
+                       array( '', 'title-invalid-empty' ),
+                       array( ':', 'title-invalid-empty' ),
+                       array( '__  __', 'title-invalid-empty' ),
+                       array( '  __  ', 'title-invalid-empty' ),
                        // Bad characters forbidden regardless of wgLegalTitleChars
-                       array( 'A [ B' ),
-                       array( 'A ] B' ),
-                       array( 'A { B' ),
-                       array( 'A } B' ),
-                       array( 'A < B' ),
-                       array( 'A > B' ),
-                       array( 'A | B' ),
+                       array( 'A [ B', 'title-invalid-characters' ),
+                       array( 'A ] B', 'title-invalid-characters' ),
+                       array( 'A { B', 'title-invalid-characters' ),
+                       array( 'A } B', 'title-invalid-characters' ),
+                       array( 'A < B', 'title-invalid-characters' ),
+                       array( 'A > B', 'title-invalid-characters' ),
+                       array( 'A | B', 'title-invalid-characters' ),
                        // URL encoding
-                       array( 'A%20B' ),
-                       array( 'A%23B' ),
-                       array( 'A%2523B' ),
+                       array( 'A%20B', 'title-invalid-characters' ),
+                       array( 'A%23B', 'title-invalid-characters' ),
+                       array( 'A%2523B', 'title-invalid-characters' ),
                        // XML/HTML character entity references
                        // Note: Commented out because they are not marked invalid by the PHP test as
                        // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first.
@@ -103,29 +103,30 @@ class TitleTest extends MediaWikiTestCase {
                        //'A &#233; B',
                        //'A &#x00E9; B',
                        // Subject of NS_TALK does not roundtrip to NS_MAIN
-                       array( 'Talk:File:Example.svg' ),
+                       array( 'Talk:File:Example.svg', 'title-invalid-talk-namespace' ),
                        // Directory navigation
-                       array( '.' ),
-                       array( '..' ),
-                       array( './Sandbox' ),
-                       array( '../Sandbox' ),
-                       array( 'Foo/./Sandbox' ),
-                       array( 'Foo/../Sandbox' ),
-                       array( 'Sandbox/.' ),
-                       array( 'Sandbox/..' ),
+                       array( '.', 'title-invalid-relative' ),
+                       array( '..', 'title-invalid-relative' ),
+                       array( './Sandbox', 'title-invalid-relative' ),
+                       array( '../Sandbox', 'title-invalid-relative' ),
+                       array( 'Foo/./Sandbox', 'title-invalid-relative' ),
+                       array( 'Foo/../Sandbox', 'title-invalid-relative' ),
+                       array( 'Sandbox/.', 'title-invalid-relative' ),
+                       array( 'Sandbox/..', 'title-invalid-relative' ),
                        // Tilde
-                       array( 'A ~~~ Name' ),
-                       array( 'A ~~~~ Signature' ),
-                       array( 'A ~~~~~ Timestamp' ),
-                       array( str_repeat( 'x', 256 ) ),
+                       array( 'A ~~~ Name', 'title-invalid-magic-tilde' ),
+                       array( 'A ~~~~ Signature', 'title-invalid-magic-tilde' ),
+                       array( 'A ~~~~~ Timestamp', 'title-invalid-magic-tilde' ),
+                       // Length
+                       array( str_repeat( 'x', 256 ), 'title-invalid-too-long' ),
                        // Namespace prefix without actual title
-                       array( 'Talk:' ),
-                       array( 'Talk:#' ),
-                       array( 'Category: ' ),
-                       array( 'Category: #bar' ),
+                       array( 'Talk:', 'title-invalid-empty' ),
+                       array( 'Talk:#', 'title-invalid-empty' ),
+                       array( 'Category: ', 'title-invalid-empty' ),
+                       array( 'Category: #bar', 'title-invalid-empty' ),
                        // interwiki prefix
-                       array( 'localtestiw: Talk: # anchor' ),
-                       array( 'localtestiw: Talk:' )
+                       array( 'localtestiw: Talk: # anchor', 'title-invalid-empty' ),
+                       array( 'localtestiw: Talk:', 'title-invalid-empty' )
                );
        }
 
@@ -164,9 +165,14 @@ class TitleTest extends MediaWikiTestCase {
         * @dataProvider provideInvalidSecureAndSplit
         * @note This mainly tests MediaWikiTitleCodec::parseTitle().
         */
-       public function testSecureAndSplitInvalid( $text ) {
+       public function testSecureAndSplitInvalid( $text, $expectedErrorMessage ) {
                $this->secureAndSplitGlobals();
-               $this->assertNull( Title::newFromText( $text ), "Invalid: $text" );
+               try {
+                       Title::newFromTextThrow( $text ); // should throw
+                       $this->assertTrue( false, "Invalid: $text" );
+               } catch ( MalformedTitleException $ex ) {
+                       $this->assertEquals( $expectedErrorMessage, $ex->getErrorMessage(), "Invalid: $text" );
+               }
        }
 
        public static function provideConvertByteClassToUnicodeClass() {