Take the site address from SITE_URL.
The feed links, the notification email, the webmaster address and the cookie domain now use it.
This commit is contained in:
+4
-2
@@ -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).
|
||||
|
||||
@@ -75,7 +75,7 @@ the centre <span class="xxx">x</span> a multiplication sign. Simply put, ten by
|
||||
</p>
|
||||
<h2>Something is broken</h2>
|
||||
<p>
|
||||
If you find anything which seems to be broken, please <a href="mailto:webmaster@xxx.org.za">email us</a>
|
||||
If you find anything which seems to be broken, please <a href="<?= hidden_email('mailto:' . site_email('webmaster')) ?>">email us</a>
|
||||
and tell us.
|
||||
</p>
|
||||
|
||||
|
||||
+36
-6
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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 '<?xml version="1.0"?>'."\n";
|
||||
echo '<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">'."\n";
|
||||
echo '<channel>'."\n";
|
||||
echo '<title>x.x.x</title>'."\n";
|
||||
echo '<link>http://xxx.org.za/</link>'."\n";
|
||||
echo '<description>Most recent articles from xxx.org.za</description>'."\n";
|
||||
echo '<link>'.$site.'/</link>'."\n";
|
||||
echo '<description>Most recent articles from '.xml_text(site_host()).'</description>'."\n";
|
||||
echo '<language>en-za</language>'."\n";
|
||||
echo '<pubDate>'.$now.'</pubDate>'."\n";
|
||||
echo '<lastBuildDate>'.date("D, d M Y H:i:s O", strtotime($most_recent[0])).'</lastBuildDate>'."\n";
|
||||
echo '<managingEditor>editor@xxx.org.za (Editors)</managingEditor>'."\n";
|
||||
echo '<webMaster>webmaster@xxx.org.za (Webmasters)</webMaster>'."\n";
|
||||
echo '<atom:link href="http://xxx.org.za/feed" rel="self" type="application/rss+xml" />'."\n";
|
||||
echo '<managingEditor>'.hidden_email(site_email('editor')).' (Editors)</managingEditor>'."\n";
|
||||
echo '<webMaster>'.hidden_email(site_email('webmaster')).' (Webmasters)</webMaster>'."\n";
|
||||
echo '<atom:link href="'.$site.'/feed" rel="self" type="application/rss+xml" />'."\n";
|
||||
echo '<item>'."\n";
|
||||
echo '<title>Front Page</title>'."\n";
|
||||
echo '<link>http://xxx.org.za/</link>'."\n";
|
||||
echo '<link>'.$site.'/</link>'."\n";
|
||||
echo '<pubDate>'.date("D, d M Y H:i:s O", strtotime($most_recent[0])).'</pubDate>'."\n";
|
||||
echo '<guid>http://xxx.org.za/</guid>'."\n";
|
||||
echo '<guid>'.$site.'/</guid>'."\n";
|
||||
echo '</item>'."\n";
|
||||
|
||||
/*
|
||||
@@ -52,14 +54,14 @@ foreach ($result as $entry) {
|
||||
$id = (int)$entry['id'];
|
||||
echo '<item>'."\n";
|
||||
echo '<title>'.xml_text($entry['title']).'</title>'."\n";
|
||||
echo '<link>http://xxx.org.za/article?id='.$id.'</link>'."\n";
|
||||
echo '<link>'.$site.'/article?id='.$id.'</link>'."\n";
|
||||
echo '<pubDate>'.date("D, d M Y H:i:s O", strtotime($entry['date'])).'</pubDate>'."\n";
|
||||
if ($entry['img'])
|
||||
echo '<description>Image: '.xml_text($entry['title']).'</description>'."\n";
|
||||
else
|
||||
echo '<description>'.xml_text(strip_tags($entry['story'])).'</description>'."\n";
|
||||
echo '<dc:creator>'.xml_text($entry['author']).'</dc:creator>'."\n";
|
||||
echo '<guid>http://xxx.org.za/article?id='.$id.'</guid>'."\n";
|
||||
echo '<guid>'.$site.'/article?id='.$id.'</guid>'."\n";
|
||||
echo '</item>'."\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -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 <editor@xxx.org.za>'."\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 .= "<Submission is an image. Review on the site.>";
|
||||
$message .= "\n\nhttp://xxx.org.za/publish/edit";
|
||||
$message .= "\n\n" . site_url() . "/publish/edit";
|
||||
mb_send_mail($to, $subject, $message, $headers);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 <ann>', (string)$items[1]->children('http://purl.org/dc/elements/1.1/')->creator);
|
||||
$this->assertSame('Image: Picture', (string)$items[2]->description);
|
||||
|
||||
@@ -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('</html>', $page['body']);
|
||||
// The webmaster's address, at the host of SITE_URL, hidden from harvesters
|
||||
$this->assertStringContainsString('<a href="' . hidden_email('mailto:webmaster@site.test') . '">email us</a>', $page['body']);
|
||||
}
|
||||
|
||||
public function testTheTestsAreNotServed(): void {
|
||||
|
||||
@@ -16,8 +16,8 @@ class EmailTest extends TestCase {
|
||||
|
||||
public function testGoesToTheEditorsFromTheQueue(): void {
|
||||
$mail = $this->send('A title', 'Ann', '<p>Hello.</p>');
|
||||
$this->assertSame('editor@xxx.org.za', $mail['headers']['to']);
|
||||
$this->assertSame('x.x.x Queue <editor@xxx.org.za>', $mail['headers']['from']);
|
||||
$this->assertSame('editor@site.test', $mail['headers']['to']);
|
||||
$this->assertSame('x.x.x Queue <editor@site.test>', $mail['headers']['from']);
|
||||
$this->assertSame('New submission: A title', $mail['subject']);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class EmailTest extends TestCase {
|
||||
$mail = $this->send('T', 'Ann', "<p>First & <strong>bold</strong>.</p>\n<p>Line one<br />line two</p><p></p><p><b> is text “quoted”</p>");
|
||||
$this->assertSame("Please review the queue. Ann has sent the following new submission:\n\n"
|
||||
. "First & bold.\n\nLine one\nline two\n\n<b> is text “quoted”"
|
||||
. "\n\nhttp://xxx.org.za/publish/edit", trim($mail['body']));
|
||||
. "\n\nhttps://site.test/publish/edit", trim($mail['body']));
|
||||
}
|
||||
|
||||
public function testLongLinesAreWrapped(): void {
|
||||
|
||||
Reference in New Issue
Block a user