From 5d657d7e45abd680c65fb1d1a5ed76e658932947 Mon Sep 17 00:00:00 2001
From: Timothy Allen
Date: Sat, 26 Sep 2026 10:04:13 +0200
Subject: [PATCH] Take the site address from SITE_URL.
The feed links, the notification email, the webmaster address and the cookie domain now use it.
---
.env.example | 6 ++--
about.php | 2 +-
config.lib.php | 42 ++++++++++++++++++++++++----
display.php | 2 +-
feed.php | 20 +++++++------
html.lib.php | 5 ++++
publish/post.lib.php | 6 ++--
session.lib.php | 6 ++--
tests/php/Pages/FeedAndImageTest.php | 6 +++-
tests/php/Pages/FrontPagesTest.php | 4 ++-
tests/php/Unit/EmailTest.php | 6 ++--
11 files changed, 75 insertions(+), 30 deletions(-)
diff --git a/.env.example b/.env.example
index 714341a..de12362 100644
--- a/.env.example
+++ b/.env.example
@@ -7,8 +7,10 @@ DB_USER=xxx
DB_PASS=
DB_NAME=xxx
-# Address of the site. The pages send plain HTTP requests to this host
-# over HTTPS. If it is empty, the pages refuse plain HTTP.
+# Address of the site. The RSS feed links, the notification email and the
+# cookie domain use it, and the pages send plain HTTP requests to this
+# host over HTTPS. The email addresses are editor@ and webmaster@ this
+# host. If it is empty, the pages refuse plain HTTP.
SITE_URL=https://xxx.org.za
# Google reCAPTCHA v2 keys (https://www.google.com/recaptcha/admin).
diff --git a/about.php b/about.php
index 05972d0..7d5932b 100644
--- a/about.php
+++ b/about.php
@@ -75,7 +75,7 @@ the centre x a multiplication sign. Simply put, ten by
Something is broken
-If you find anything which seems to be broken, please email us
+If you find anything which seems to be broken, please email us
and tell us.
diff --git a/config.lib.php b/config.lib.php
index 6b01fe1..d7b582b 100644
--- a/config.lib.php
+++ b/config.lib.php
@@ -41,20 +41,50 @@ function config($key, $default = NULL) {
}
/*
- * Send a plain HTTP request to the same page over HTTPS, and stop. The
- * host comes from SITE_URL, because the browser controls the Host header.
+ * The address of the site comes from SITE_URL, never from the Host
+ * header, which the browser controls. site_url() gives the scheme, host
+ * and port (for example "https://xxx.org.za"), and site_host() gives the
+ * host. Both are empty if SITE_URL has no host.
+ */
+function site_url_parts() {
+ $site = parse_url(trim((string)config('SITE_URL', '')));
+ return is_array($site) && !empty($site['host']) ? $site : array();
+}
+
+function site_host() {
+ return site_url_parts()['host'] ?? '';
+}
+
+function site_url($scheme = NULL) {
+ $site = site_url_parts();
+ if (!$site)
+ return '';
+ $port = isset($site['port']) ? ':' . $site['port'] : '';
+ return ($scheme ?? $site['scheme'] ?? 'https') . '://' . $site['host'] . $port;
+}
+
+// An address at the site's host, such as "editor@xxx.org.za"
+function site_email($name) {
+ return $name . '@' . site_host();
+}
+
+// The cookie domain: the site's host and its subdomains
+function cookie_domain() {
+ return site_host() === '' ? '' : '.' . site_host();
+}
+
+/*
+ * Send a plain HTTP request to the same page over HTTPS, and stop.
* Without SITE_URL, the page refuses plain HTTP.
*/
function require_https() {
if (isset($_SERVER['HTTPS']))
return;
- $site = parse_url((string)config('SITE_URL', ''));
- if (empty($site['host'])) {
+ if (site_url() === '') {
http_response_code(403);
echo "This page needs HTTPS.\n";
} else {
- $port = isset($site['port']) ? ':' . $site['port'] : '';
- header('Location: https://' . $site['host'] . $port . $_SERVER['REQUEST_URI']);
+ header('Location: ' . site_url('https') . $_SERVER['REQUEST_URI']);
}
exit;
}
diff --git a/display.php b/display.php
index beffd0f..a07de23 100644
--- a/display.php
+++ b/display.php
@@ -5,7 +5,7 @@ require_once('./get_data.lib.php');
$queue = get_queue();
if ($queue) session_cache_limiter('nocache');
else session_cache_limiter('none');
-session_set_cookie_params(array('lifetime' => 0, 'path' => '/', 'domain' => '.xxx.org.za', 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
+session_set_cookie_params(array('lifetime' => 0, 'path' => '/', 'domain' => cookie_domain(), 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
session_start();
if (isset($_SESSION['editor'])) {
$is_admin = TRUE;
diff --git a/feed.php b/feed.php
index 914ba36..564c60c 100644
--- a/feed.php
+++ b/feed.php
@@ -10,6 +10,8 @@ $show = get_show_num();
$table = "xxx";
header("Content-Type: application/rss+xml");
+$site = xml_text(site_url());
+
// Set up a RSS "header"
$query = "SELECT MAX(`date`) FROM $table;";
@@ -21,19 +23,19 @@ echo ''."\n";
echo ''."\n";
echo ''."\n";
echo 'x.x.x'."\n";
- echo 'http://xxx.org.za/'."\n";
- echo 'Most recent articles from xxx.org.za'."\n";
+ echo ''.$site.'/'."\n";
+ echo 'Most recent articles from '.xml_text(site_host()).''."\n";
echo 'en-za'."\n";
echo ''.$now.''."\n";
echo ''.date("D, d M Y H:i:s O", strtotime($most_recent[0])).''."\n";
- echo 'editor@xxx.org.za (Editors)'."\n";
- echo 'webmaster@xxx.org.za (Webmasters)'."\n";
- echo ''."\n";
+ echo ''.hidden_email(site_email('editor')).' (Editors)'."\n";
+ echo ''.hidden_email(site_email('webmaster')).' (Webmasters)'."\n";
+ echo ''."\n";
echo ''."\n";
echo 'Front Page'."\n";
- echo 'http://xxx.org.za/'."\n";
+ echo ''.$site.'/'."\n";
echo ''.date("D, d M Y H:i:s O", strtotime($most_recent[0])).''."\n";
- echo 'http://xxx.org.za/'."\n";
+ echo ''.$site.'/'."\n";
echo ''."\n";
/*
@@ -52,14 +54,14 @@ foreach ($result as $entry) {
$id = (int)$entry['id'];
echo ''."\n";
echo ''.xml_text($entry['title']).''."\n";
- echo 'http://xxx.org.za/article?id='.$id.''."\n";
+ echo ''.$site.'/article?id='.$id.''."\n";
echo ''.date("D, d M Y H:i:s O", strtotime($entry['date'])).''."\n";
if ($entry['img'])
echo 'Image: '.xml_text($entry['title']).''."\n";
else
echo ''.xml_text(strip_tags($entry['story'])).''."\n";
echo ''.xml_text($entry['author']).''."\n";
- echo 'http://xxx.org.za/article?id='.$id.''."\n";
+ echo ''.$site.'/article?id='.$id.''."\n";
echo ''."\n";
}
diff --git a/html.lib.php b/html.lib.php
index c8b0669..92bc0bb 100644
--- a/html.lib.php
+++ b/html.lib.php
@@ -8,4 +8,9 @@ function h($string) {
function form_value($string) {
return htmlspecialchars((string)$string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
+
+// Text as numeric character references, which hides an email address from simple harvesters
+function hidden_email($address) {
+ return preg_replace_callback('/./s', function ($char) { return '' . ord($char[0]) . ';'; }, (string)$address);
+}
?>
diff --git a/publish/post.lib.php b/publish/post.lib.php
index 99b2c73..d7fa539 100644
--- a/publish/post.lib.php
+++ b/publish/post.lib.php
@@ -261,9 +261,9 @@ function sanitise($story) {
function send_email($title, $author, $story) {
include_once('../lib/convert_entities.inc.php');
- $to = 'editor@xxx.org.za';
+ $to = site_email('editor');
$subject = "New submission: $title";
- $headers = 'From: x.x.x Queue '."\r\n";
+ $headers = 'From: x.x.x Queue <'.site_email('editor').'>'."\r\n";
$message = "Please review the queue. $author has sent the following new submission:\n\n";
if ($story) {
// Turn line and paragraph breaks into new lines before the tags are removed,
@@ -275,6 +275,6 @@ function send_email($title, $author, $story) {
}
else
$message .= "";
- $message .= "\n\nhttp://xxx.org.za/publish/edit";
+ $message .= "\n\n" . site_url() . "/publish/edit";
mb_send_mail($to, $subject, $message, $headers);
}
diff --git a/session.lib.php b/session.lib.php
index 5a58625..3448949 100644
--- a/session.lib.php
+++ b/session.lib.php
@@ -7,7 +7,7 @@ $is_admin = FALSE;
if (isset($_SERVER['HTTPS'])) {
session_cache_limiter('private');
- session_set_cookie_params(array('lifetime' => 0, 'path' => '/', 'domain' => '.xxx.org.za', 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
+ session_set_cookie_params(array('lifetime' => 0, 'path' => '/', 'domain' => cookie_domain(), 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
session_start();
if (isset($_SESSION['editor'])) {
@@ -83,7 +83,7 @@ function create_persistent_login($username) {
open_db('close');
// Create cookie with key
- setcookie('PersistentAuth', "$username|$key", array('expires' => $validity, 'path' => '/', 'domain' => '.xxx.org.za', 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
+ setcookie('PersistentAuth', "$username|$key", array('expires' => $validity, 'path' => '/', 'domain' => cookie_domain(), 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
return true;
}
@@ -110,7 +110,7 @@ function delete_persistent_login() {
open_db('close');
// Delete cookie
- setcookie("PersistentAuth", '', array('expires' => strtotime('-1 year'), 'path' => '/', 'domain' => '.xxx.org.za', 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
+ setcookie("PersistentAuth", '', array('expires' => strtotime('-1 year'), 'path' => '/', 'domain' => cookie_domain(), 'secure' => TRUE, 'httponly' => TRUE, 'samesite' => 'Lax'));
return true;
}
diff --git a/tests/php/Pages/FeedAndImageTest.php b/tests/php/Pages/FeedAndImageTest.php
index 31cd237..5150726 100644
--- a/tests/php/Pages/FeedAndImageTest.php
+++ b/tests/php/Pages/FeedAndImageTest.php
@@ -31,7 +31,11 @@ class FeedAndImageTest extends PageTestCase {
// The front page, then the visible stories, newest first
$this->assertSame(array('Front Page', 'Tom & "Jerry"', 'Picture', 'Not an image'),
array_map('strval', iterator_to_array($xml->xpath('/rss/channel/item/title'), FALSE)));
- $this->assertSame('http://xxx.org.za/article?id=1', (string)$items[1]->link);
+ // The addresses come from SITE_URL
+ $this->assertSame('https://site.test/', (string)$xml->channel->link);
+ $this->assertSame('https://site.test/article?id=1', (string)$items[1]->link);
+ $this->assertSame('editor@site.test (Editors)', (string)$xml->channel->managingEditor);
+ $this->assertStringContainsString('edi', $feed['body']);
$this->assertSame('Wait… a bold move & more.', (string)$items[1]->description);
$this->assertSame('Ann ', (string)$items[1]->children('http://purl.org/dc/elements/1.1/')->creator);
$this->assertSame('Image: Picture', (string)$items[2]->description);
diff --git a/tests/php/Pages/FrontPagesTest.php b/tests/php/Pages/FrontPagesTest.php
index d3fa0e0..f7744ef 100644
--- a/tests/php/Pages/FrontPagesTest.php
+++ b/tests/php/Pages/FrontPagesTest.php
@@ -32,7 +32,7 @@ class FrontPagesTest extends PageTestCase {
public function testTheSessionCookieIsSecure(): void {
$cookie = $this->header($this->get('/'), 'Set-Cookie');
$this->assertMatchesRegularExpression('/^PHPSESSID=[0-9a-z,-]+;/', $cookie);
- foreach (array('path=/', 'domain=.xxx.org.za', 'secure', 'HttpOnly', 'SameSite=Lax') as $flag)
+ foreach (array('path=/', 'domain=.site.test', 'secure', 'HttpOnly', 'SameSite=Lax') as $flag)
$this->assertStringContainsString("; $flag", $cookie);
}
@@ -89,6 +89,8 @@ class FrontPagesTest extends PageTestCase {
$page = $this->get('/about');
$this->assertSame(200, $page['status']);
$this->assertStringContainsString('