Fix Title::getFragmentForURL for bad interwiki prefix.
authordaniel <dkinzler@wikimedia.org>
Wed, 23 Jan 2019 10:17:21 +0000 (11:17 +0100)
committerdaniel <dkinzler@wikimedia.org>
Wed, 23 Jan 2019 10:17:21 +0000 (11:17 +0100)
Calling Title::getLinkURL or any other method that relies on
getFragmentForURL on a title with an unknown interwiki prefix
was triggering a fatal error. With this patch, that situation is
handled more gracefully.

Bug: T204800
Change-Id: I665cd5e983a80c15c68c89541d9c856082c460bb

includes/Title.php
tests/phpunit/includes/TitleMethodsTest.php

index 3496668..1bb54df 100644 (file)
@@ -1609,11 +1609,15 @@ class Title implements LinkTarget, IDBAccessObject {
        public function getFragmentForURL() {
                if ( !$this->hasFragment() ) {
                        return '';
-               } elseif ( $this->isExternal()
-                       && !self::getInterwikiLookup()->fetch( $this->mInterwiki )->isLocal()
-               ) {
-                       return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
+               } elseif ( $this->isExternal() ) {
+                       // Note: If the interwiki is unknown, it's treated as a namespace on the local wiki,
+                       // so we treat it like a local interwiki.
+                       $interwiki = self::getInterwikiLookup()->fetch( $this->mInterwiki );
+                       if ( $interwiki && !$interwiki->isLocal() ) {
+                               return '#' . Sanitizer::escapeIdForExternalInterwiki( $this->mFragment );
+                       }
                }
+
                return '#' . Sanitizer::escapeIdForLink( $this->mFragment );
        }
 
index 44d440f..25dc9b3 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use MediaWiki\Interwiki\InterwikiLookup;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -353,6 +354,113 @@ class TitleMethodsTest extends MediaWikiLangTestCase {
                $this->assertEquals( 0, $linkCache->getGoodLinkID( 'Foo' ), 'link cache should be empty' );
        }
 
+       public function provideGetLinkURL() {
+               yield 'Simple' => [
+                       '/wiki/Goats',
+                       NS_MAIN,
+                       'Goats'
+               ];
+
+               yield 'Fragment' => [
+                       '/wiki/Goats#Goatificatiön',
+                       NS_MAIN,
+                       'Goats',
+                       'Goatificatiön'
+               ];
+
+               yield 'Unknown interwiki with fragment' => [
+                       'https://xx.wiki.test/wiki/xyzzy:Goats#Goatificatiön',
+                       NS_MAIN,
+                       'Goats',
+                       'Goatificatiön',
+                       'xyzzy'
+               ];
+
+               yield 'Interwiki with fragment' => [
+                       'https://acme.test/Goats#Goatificati.C3.B6n',
+                       NS_MAIN,
+                       'Goats',
+                       'Goatificatiön',
+                       'acme'
+               ];
+
+               yield 'Interwiki with query' => [
+                       'https://acme.test/Goats?a=1&b=blank+blank#Goatificati.C3.B6n',
+                       NS_MAIN,
+                       'Goats',
+                       'Goatificatiön',
+                       'acme',
+                       [
+                               'a' => 1,
+                               'b' => 'blank blank'
+                       ]
+               ];
+
+               yield 'Local interwiki with fragment' => [
+                       'https://yy.wiki.test/wiki/Goats#Goatificatiön',
+                       NS_MAIN,
+                       'Goats',
+                       'Goatificatiön',
+                       'yy'
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetLinkURL
+        *
+        * @covers Title::getLinkURL
+        * @covers Title::getFullURL
+        * @covers Title::getLocalURL
+        * @covers Title::getFragmentForURL
+        */
+       public function testGetLinkURL(
+               $expected,
+               $ns,
+               $title,
+               $fragment = '',
+               $interwiki = '',
+               $query = '',
+               $query2 = false,
+               $proto = false
+       ) {
+               $this->setMwGlobals( [
+                       'wgServer' => 'https://xx.wiki.test',
+                       'wgArticlePath' => '/wiki/$1',
+                       'wgExternalInterwikiFragmentMode' => 'legacy',
+                       'wgFragmentMode' => [ 'html5', 'legacy' ]
+               ] );
+
+               $interwikiLookup = $this->getMock( InterwikiLookup::class );
+
+               $interwikiLookup->method( 'fetch' )
+                       ->willReturnCallback( function ( $interwiki ) {
+                               switch ( $interwiki ) {
+                                       case '':
+                                               return null;
+                                       case 'acme':
+                                               return new Interwiki(
+                                                       'acme',
+                                                       'https://acme.test/$1'
+                                               );
+                                       case 'yy':
+                                               return new Interwiki(
+                                                       'yy',
+                                                       'https://yy.wiki.test/wiki/$1',
+                                                       '/w/api.php',
+                                                       'yywiki',
+                                                       true
+                                               );
+                                       default:
+                                               return false;
+                               }
+                       } );
+
+               $this->setService( 'InterwikiLookup', $interwikiLookup );
+
+               $title = Title::makeTitle( $ns, $title, $fragment, $interwiki );
+               $this->assertSame( $expected, $title->getLinkURL( $query, $query2, $proto ) );
+       }
+
        function tearDown() {
                Title::clearCaches();
                parent::tearDown();