From 387cc40f84368e736fa043115bc2aacac515be41 Mon Sep 17 00:00:00 2001 From: NickSdot Date: Sun, 19 Jul 2026 23:04:21 +0700 Subject: [PATCH 1/2] Canonicalise feed base URL handling --- group.php | 19 ++++++++++++------- lib/config.php | 7 +++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/group.php b/group.php index 4884b1c..005941e 100644 --- a/group.php +++ b/group.php @@ -29,15 +29,18 @@ error($e->getMessage()); } -$host = htmlspecialchars($_SERVER['HTTP_HOST'], ENT_QUOTES, "UTF-8"); +$cleanBaseUrl = clean($NEWS_WEB_BASE_URL); +$baseUrlParts = parse_url($NEWS_WEB_BASE_URL); +$cleanBaseHost = clean($baseUrlParts['host'] . (isset($baseUrlParts['port']) ? ':' . $baseUrlParts['port'] : '')); +$cleanGroupUrl = urlencode($group); switch ($format) { case 'rss': header("Content-type: text/xml"); echo '' . "\n";?> - <?php echo $host; ?>: <?php echo $group?> - http:///group.php?group= + <?php echo $cleanBaseHost; ?>: <?php echo $group?> + /group.php?group= - <?php echo $host; ?>: <?php echo $group?> - http:///group.php?group= + <?php echo $cleanBaseHost; ?>: <?php echo $group?> + /group.php?group= Newsgroup at en-US @@ -146,11 +149,13 @@ foreach ($overview['articles'] as $articleNumber => $details) { /* $date = date("H:i:s M/d/y", strtotime($odate)); */ $date822 = date("r", strtotime($details['date'])); + $cleanArticlePath = "/$cleanGroupUrl/" . urlencode((string) $articleNumber); + $cleanArticleLink = "$cleanBaseUrl$cleanArticlePath"; switch ($format) { case 'rss': echo " \n"; - echo " http://$host/$group/$articleNumber\n"; + echo " $cleanArticleLink\n"; echo " ", format_subject($details['subject'], $charset), "\n"; echo " ", htmlspecialchars(format_author($details['author'], $charset), ENT_QUOTES, "UTF-8"), @@ -161,7 +166,7 @@ case 'rdf': echo " \n"; echo " ", format_subject($details['subject'], $charset), "\n"; - echo " http://$host/$group/$articleNumber\n"; + echo " $cleanArticleLink\n"; echo " ", htmlspecialchars(format_author($details['author'], $charset), ENT_QUOTES, "UTF-8"), "\n"; diff --git a/lib/config.php b/lib/config.php index 71a3835..dc80df9 100644 --- a/lib/config.php +++ b/lib/config.php @@ -4,3 +4,10 @@ if (getenv('NNTP_HOST')) { $NNTP_HOST = getenv('NNTP_HOST'); } + +$NEWS_WEB_BASE_URL = 'https://news.php.net'; +if (getenv('NEWS_WEB_BASE_URL')) { + $NEWS_WEB_BASE_URL = rtrim(getenv('NEWS_WEB_BASE_URL'), '/'); +} elseif (PHP_SAPI == 'cli-server') { + $NEWS_WEB_BASE_URL = 'http://' . $_SERVER['HTTP_HOST']; +} From 4227758b3ce4d2ff3117cab354f061a8ebe77272 Mon Sep 17 00:00:00 2001 From: NickSdot Date: Sun, 19 Jul 2026 23:07:24 +0700 Subject: [PATCH 2/2] Apply additional output hardening --- article.php | 26 ++++++++++++++------------ group.php | 25 +++++++++++++------------ index.php | 11 ++++++----- lib/ThreadTree.php | 7 ++++--- lib/common.php | 21 +++++++++++++-------- lib/group-navbar.php | 6 +++--- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/article.php b/article.php index 7db4dbd..a9d5760 100644 --- a/article.php +++ b/article.php @@ -60,14 +60,17 @@ } head("{$group}: " . format_title($mail['headers']['subject'], 'utf-8')); +$cleanGroupUrl = urlencode($group); +$cleanGroup = clean($group); + echo ''; @@ -107,7 +110,7 @@ echo ' '; $r = explode(",", rtrim($mail['headers']['newsgroups'])); foreach ($r as $v) { - echo "" . htmlspecialchars($v) . " "; + echo "" . clean($v) . " "; } echo "\n"; } @@ -331,7 +334,7 @@ $name = $attachment['filename']; if ($mimetype == 'text/plain') { - echo htmlspecialchars($attachment['data']); + echo clean($attachment['data']); continue; } @@ -347,14 +350,14 @@ $link_desc .= " " . $description; } - $dl_link = "/getpart.php?group=$group&article=$article&part=$mimecount"; - $link_desc = htmlspecialchars($link_desc, ENT_QUOTES, 'UTF-8'); + $dl_link = "/getpart.php?group=" . urlencode($group) . "&article=" . urlencode((string) $article) . "&part=" . urlencode((string) $mimecount); + $link_desc = clean($link_desc); /* Attachment filename and mimetype might contain malicious characters */ printf( 'Attachment: %s
' . "\n", $dl_link, - htmlspecialchars($link_desc) + $link_desc, ); } } @@ -380,7 +383,6 @@ // Does not check existence of next, so consider this the super duper fast [broken] version // Based off navbar() in group.php -$group = htmlspecialchars($group, ENT_QUOTES, "UTF-8"); $current = $article; echo ' ' . "\n"; @@ -388,15 +390,15 @@ echo ' ' . "\n"; -echo ' \n"; +echo ' \n"; echo ' ' . "\n"; echo ' ' . "\n"; echo '
'; if ($current > 1) { - echo ' « previous'; + echo ' « previous'; } else { echo ' '; } echo ' ' . "$group (#$current)' . $cleanGroup . " (#$current)'; -echo ' next »'; +echo ' next »'; echo '
' . "\n"; diff --git a/group.php b/group.php index 005941e..fee98b1 100644 --- a/group.php +++ b/group.php @@ -33,13 +33,14 @@ $baseUrlParts = parse_url($NEWS_WEB_BASE_URL); $cleanBaseHost = clean($baseUrlParts['host'] . (isset($baseUrlParts['port']) ? ':' . $baseUrlParts['port'] : '')); $cleanGroupUrl = urlencode($group); +$cleanGroupHtml = clean($group); switch ($format) { case 'rss': header("Content-type: text/xml"); echo '' . "\n";?> - <?php echo $cleanBaseHost; ?>: <?php echo $group?> + <?php echo $cleanBaseHost; ?>: <?php echo $cleanGroupHtml?> /group.php?group= - <?php echo $cleanBaseHost; ?>: <?php echo $group?> + <?php echo $cleanBaseHost; ?>: <?php echo $cleanGroupHtml?> /group.php?group= - Newsgroup at + Newsgroup at en-US '; echo ' '; echo ' '; + $cleanGroupUrl . '">', + $cleanGroupHtml . ''; echo ' '; echo ''; echo '
'; - echo '

' . htmlspecialchars($group, ENT_QUOTES, "UTF-8") . '

'; + echo '

' . $cleanGroupHtml . '

'; if ($i == 0) { /* Special header of info for the main page for a group */ $groups = $nntpClient->listGroups($group); @@ -92,7 +93,7 @@

@@ -158,7 +159,7 @@ echo " $cleanArticleLink\n"; echo " ", format_subject($details['subject'], $charset), "\n"; echo " ", - htmlspecialchars(format_author($details['author'], $charset), ENT_QUOTES, "UTF-8"), + clean(format_author($details['author'], $charset)), "\n"; echo " $date822\n"; echo " \n"; @@ -168,7 +169,7 @@ echo " ", format_subject($details['subject'], $charset), "\n"; echo " $cleanArticleLink\n"; echo " ", - htmlspecialchars(format_author($details['author'], $charset), ENT_QUOTES, "UTF-8"), + clean(format_author($details['author'], $charset)), "\n"; echo " $date822\n"; echo " \n"; @@ -176,14 +177,14 @@ case 'html': default: echo " \n"; - echo " $articleNumber\n"; - echo " "; + echo " " . clean($articleNumber) . "\n"; + echo " "; echo format_subject($details['subject'], $charset); echo "\n"; echo " " . format_author($details['author'], $charset) . "\n"; echo " " . format_date($details['date']) . "\n"; - echo " {$details['lines']}\n"; + echo " " . clean($details['lines']) . "\n"; echo " \n"; } } diff --git a/index.php b/index.php index 1c9d1e3..1e0e765 100644 --- a/index.php +++ b/index.php @@ -17,7 +17,7 @@ head(); -$DISPLAY_NNTP_HOST = htmlspecialchars(($NNTP_HOST == 'localhost') ? 'news.php.net' : $NNTP_HOST); +$CLEAN_DISPLAY_NNTP_HOST = clean(($NNTP_HOST == 'localhost') ? 'news.php.net' : $NNTP_HOST); ?>
\n"; foot(); die(); } /* Borrowed from web-php repo. */ -function clean($var) +function clean($var): string { - return htmlspecialchars($var, \ENT_QUOTES); + return htmlspecialchars((string) $var, ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8"); } // Try to check that this email address is valid @@ -112,7 +112,7 @@ function head($title = "PHP Mailing Lists (PHP News)") - <?php echo htmlspecialchars($title); ?> + <?php echo clean($title); ?> @@ -246,7 +246,11 @@ function format_author($a, $charset = 'iso-8859-1', $nameOnly = false) $email= spam_protect($ar[1]); $name = $ar[2]; } - elseif (preg_match("/^\s*\"?(.+?)\"?\s*<(.+)>\s*$/", $a, $ar)) { + + // Treat the address as one angle-bracketed part, so extra + // brackets in the display name do not confuse parsing. + + elseif (preg_match("/^\s*\"?(.+?)\"?\s*<([^<>]+)>\s*$/", $a, $ar)) { $email = spam_protect($ar[2]); $name = $ar[1]; } @@ -255,13 +259,14 @@ function format_author($a, $charset = 'iso-8859-1', $nameOnly = false) } else { $email = $name = $a; } + $name = clean($name); if ($nameOnly) { return str_replace(" ", " ", $name); } else { return "" . str_replace(" ", " ", $name) . ""; } @@ -281,7 +286,7 @@ function format_subject($s, $charset = 'iso-8859-1', $trimRe = false) } else { $s = wordwrap($s, 150); } - return nl2br(htmlspecialchars($s, ENT_QUOTES, "UTF-8")); + return nl2br(clean($s)); } @@ -296,7 +301,7 @@ function format_title($s, $charset = 'iso-8859-1', $trimRe = false) } else { $s = wordwrap($s, 150); } - return htmlspecialchars($s, ENT_QUOTES, "UTF-8"); + return clean($s); } function format_date($d, $format = 'r') diff --git a/lib/group-navbar.php b/lib/group-navbar.php index e8d92bc..cfaad43 100644 --- a/lib/group-navbar.php +++ b/lib/group-navbar.php @@ -7,7 +7,7 @@ function navbar($g, $f, $l, $i) echo ' '; if ($i > $f) { $p = max($i - 20, $f); - echo "", + echo "", "« previous"; } else { echo " "; @@ -15,11 +15,11 @@ function navbar($g, $f, $l, $i) echo '' . "\n"; $j = min($i + 20, $l); $c = $l - $f + 1; - echo ' ' . htmlspecialchars($g, ENT_QUOTES, "UTF-8") . " ($i-$j of $c)\n"; + echo ' ' . clean($g) . " ($i-$j of $c)\n"; echo ' '; if ($i + 20 <= $l) { $n = min($i + 20, $l - 19); - echo "", + echo "", "next »"; } else { echo " ";