Escape % sign if form valid percent-encoding in fragment identifiers

Currently if you combine a valid percent encoding and a non
escaped character that is reserved in urls in a headline, the toc
link does not work. E.g. ==`%41== needs #`%2541 but we currently
generate #`%41 which matches ==`A== instead.

Tested in firefox and chrome

Bug: T238385
Change-Id: Ice2bbf79bed612d488ed6feb7510035e9dfb33af
This commit is contained in:
Brian Wolff 2020-02-15 01:24:10 -08:00
parent a18d0fe181
commit 28d44262aa
3 changed files with 33 additions and 14 deletions

View file

@ -1291,7 +1291,7 @@ class Sanitizer {
$mode = $wgFragmentMode[self::ID_PRIMARY];
$id = self::escapeIdInternal( $id, $mode );
$id = self::escapeIdInternalUrl( $id, $mode );
return $id;
}
@ -1308,11 +1308,28 @@ class Sanitizer {
public static function escapeIdForExternalInterwiki( $id ) {
global $wgExternalInterwikiFragmentMode;
$id = self::escapeIdInternal( $id, $wgExternalInterwikiFragmentMode );
$id = self::escapeIdInternalUrl( $id, $wgExternalInterwikiFragmentMode );
return $id;
}
/**
* Do percent encoding of percent signs for href (but not id) attributes
*
* @since 1.35
* @see https://phabricator.wikimedia.org/T238385
* @param string $id String to escape
* @param string $mode One of modes from $wgFragmentMode
* @return string
*/
private static function escapeIdInternalUrl( $id, $mode ) {
$id = self::escapeIdInternal( $id, $mode );
if ( $mode === 'html5' ) {
$id = preg_replace( '/%([a-fA-F0-9]{2})/', '%25$1', $id );
}
return $id;
}
/**
* Helper for escapeIdFor*() functions. Performs most of the actual escaping.
*

View file

@ -22858,7 +22858,7 @@ wgFragmentMode=[ 'html5', 'legacy' ]
__NOEDITSECTION__
!! html/php
<h3><span id=".2B:.3A.253A_.26.26.5D.5D_x"></span><span class="mw-headline" id="+:.3A%3A_&amp;&amp;]]_x">_ +:.3A%3A _ &amp;&amp;]] x</span></h3>
<p>+:.3A%3A_&amp;&amp;&#93;&#93;_x
<p>+:.3A%253A_&amp;&amp;&#93;&#93;_x
</p>
!! html/parsoid
<h3 id="+:.3A%3A_&amp;&amp;]]_x"><span id=".2B:.3A.253A_.26.26.5D.5D_x" typeof="mw:FallbackId"></span>_ +:.3A%3A _ &amp;<span typeof="mw:Entity" data-parsoid='{"src":"&amp;amp;","srcContent":"&amp;","dsr":[18,23,null,null]}'>&amp;</span>]] x</h3>

View file

@ -173,6 +173,7 @@ class SanitizerTest extends MediaWikiTestCase {
* @covers Sanitizer::escapeIdForLink()
* @covers Sanitizer::escapeIdForExternalInterwiki()
* @covers Sanitizer::escapeIdInternal()
* @covers Sanitizer::escapeIdInternalUrl()
*
* @param string $stuff
* @param string[] $config
@ -193,10 +194,11 @@ class SanitizerTest extends MediaWikiTestCase {
public function provideEscapeIdForStuff() {
// Test inputs and outputs
$text = 'foo тест_#%!\'()[]:<>&&amp;&amp;amp;';
$text = 'foo тест_#%!\'()[]:<>&&amp;&amp;amp;%F0';
$legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E' .
'.26.26amp.3B.26amp.3Bamp.3B';
$html5Encoded = 'foo_тест_#%!\'()[]:<>&&amp;&amp;amp;';
'.26.26amp.3B.26amp.3Bamp.3B.25F0';
$html5EncodedId = 'foo_тест_#%!\'()[]:<>&&amp;&amp;amp;%F0';
$html5EncodedHref = 'foo_тест_#%!\'()[]:<>&&amp;&amp;amp;%25F0';
// Settings: last element is $wgExternalInterwikiFragmentMode, the rest is $wgFragmentMode
$legacy = [ 'legacy', 'legacy' ];
@ -214,27 +216,27 @@ class SanitizerTest extends MediaWikiTestCase {
// Transition to a new world: legacy links with HTML5 fallback
[ 'Attribute', $legacyNew, $text, $legacyEncoded, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $legacyNew, $text, $html5Encoded, Sanitizer::ID_FALLBACK ],
[ 'Attribute', $legacyNew, $text, $html5EncodedId, Sanitizer::ID_FALLBACK ],
[ 'Link', $legacyNew, $text, $legacyEncoded ],
[ 'ExternalInterwiki', $legacyNew, $text, $legacyEncoded ],
// New world: HTML5 links, legacy fallbacks
[ 'Attribute', $newLegacy, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $newLegacy, $text, $html5EncodedId, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $newLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
[ 'Link', $newLegacy, $text, $html5Encoded ],
[ 'Link', $newLegacy, $text, $html5EncodedHref ],
[ 'ExternalInterwiki', $newLegacy, $text, $legacyEncoded ],
// Distant future: no legacy fallbacks, but still linking to leagacy wikis
[ 'Attribute', $new, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $new, $text, $html5EncodedId, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $new, $text, false, Sanitizer::ID_FALLBACK ],
[ 'Link', $new, $text, $html5Encoded ],
[ 'Link', $new, $text, $html5EncodedHref ],
[ 'ExternalInterwiki', $new, $text, $legacyEncoded ],
// Just before the heat death of universe: external interwikis are also HTML5 \m/
[ 'Attribute', $allNew, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $allNew, $text, $html5EncodedId, Sanitizer::ID_PRIMARY ],
[ 'Attribute', $allNew, $text, false, Sanitizer::ID_FALLBACK ],
[ 'Link', $allNew, $text, $html5Encoded ],
[ 'ExternalInterwiki', $allNew, $text, $html5Encoded ],
[ 'Link', $allNew, $text, $html5EncodedHref ],
[ 'ExternalInterwiki', $allNew, $text, $html5EncodedHref ],
];
}