From c6bb514ebbc8cfdefbaaa38935b831e72cb8f2f6 Mon Sep 17 00:00:00 2001
From: Lucas Werkmeister <lucas.werkmeister@wikimedia.de>
Date: Wed, 14 Jun 2023 17:01:41 +0200
Subject: [PATCH] SECURITY: Escape badge title
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For the badge title= attribute, we use the plain text item label, but
weren’t escaping it previously. Fix that and add a test.

Bug: T339111
Change-Id: I535a7577df9882cf199407a68dc768febe076eaa
---
 view/src/SiteLinksView.php               |  2 +-
 view/tests/phpunit/SiteLinksViewTest.php | 19 +++++++++++++++++--
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/view/src/SiteLinksView.php b/view/src/SiteLinksView.php
index 5864120618..249313783e 100644
--- a/view/src/SiteLinksView.php
+++ b/view/src/SiteLinksView.php
@@ -357,7 +357,7 @@ private function getHtmlForBadges( array $badges ) {
 
 			$html .= $this->templateFactory->render( 'wb-badge',
 				$classes,
-				$this->entityIdFormatter->formatEntityId( $badge ),
+				Sanitizer::safeEncodeAttribute( $this->entityIdFormatter->formatEntityId( $badge ) ),
 				$badge
 			);
 		}
diff --git a/view/tests/phpunit/SiteLinksViewTest.php b/view/tests/phpunit/SiteLinksViewTest.php
index f637351c20..1735cd691e 100644
--- a/view/tests/phpunit/SiteLinksViewTest.php
+++ b/view/tests/phpunit/SiteLinksViewTest.php
@@ -101,13 +101,15 @@ public function testTwoSiteLinks() {
 	public function testBadges() {
 		$featured = new ItemId( 'Q42' );
 		$good = new ItemId( 'Q12' );
+		$withHtml = new ItemId( 'Q56' );
 		$siteLinks = [
 			new SiteLink( 'enwiki', 'Title', [ $featured ] ),
 			new SiteLink( 'dewiki', 'Titel', [ $featured, $good ] ),
+			new SiteLink( 'ptwiki', 'tItLe', [ $withHtml ] ),
 		];
 		$html = $this->newInstance()->getHtml( $siteLinks, null, [ 'wikipedia' ] );
 
-		$this->assertSame( 3, substr_count( $html, '<BADGE' ) );
+		$this->assertSame( 4, substr_count( $html, '<BADGE' ) );
 		$this->assertStringContainsString(
 			'<BADGE class="Q42 wb-badge-featuredarticle" id="Q42">Featured article</BADGE>',
 			$html
@@ -116,6 +118,11 @@ public function testBadges() {
 			'<BADGE class="Q12 wb-badge-goodarticle" id="Q12">Q12</BADGE>',
 			$html
 		);
+		$this->assertStringContainsString(
+			'<BADGE class="Q56" id="Q56">badge &lt;with&gt; &quot;HTML&quot;</BADGE>',
+			$html
+		);
+		$this->assertStringNotContainsString( '<with>', $html );
 	}
 
 	/**
@@ -183,7 +190,13 @@ private function newSiteList() {
 		$deWiki->setLanguageCode( 'de' );
 		$deWiki->setGroup( 'wikipedia' );
 
-		return new SiteList( [ $enWiki, $specialWiki, $deWiki ] );
+		$ptWiki = new Site();
+		$ptWiki->setGlobalId( 'ptwiki' );
+		$ptWiki->setLinkPath( '#ptwiki' );
+		$ptWiki->setLanguageCode( 'pt' );
+		$ptWiki->setGroup( 'wikipedia' );
+
+		return new SiteList( [ $enWiki, $specialWiki, $deWiki, $ptWiki ] );
 	}
 
 	/**
@@ -196,6 +209,8 @@ private function newEntityIdFormatter() {
 			->willReturnCallback( function( EntityId $id ) {
 				if ( $id->getSerialization() === 'Q42' ) {
 					return 'Featured article';
+				} elseif ( $id->getSerialization() === 'Q56' ) {
+					return 'badge <with> "HTML"';
 				}
 
 				return $id->getSerialization();
-- 
2.39.2

