Clean up escaping, linking, and feeds.
This commit is contained in:
parent
856677659e
commit
b59526c590
@ -1,20 +1,30 @@
|
||||
<?php
|
||||
class Util {
|
||||
public static function escape_and_linkify(string $text, int $flags = ENT_NOQUOTES | ENT_HTML5, bool $new_window = true ): string {
|
||||
// escape dangerous characters, but preserve quotes
|
||||
$safe = htmlspecialchars($text, $flags, 'UTF-8');
|
||||
public static function escape_html(string $text): string {
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
public static function escape_xml(string $text): string {
|
||||
return htmlspecialchars($text, ENT_QUOTES | ENT_XML1, 'UTF-8');
|
||||
}
|
||||
|
||||
// Convert URLs in text to links (anchor tags)
|
||||
// NOTE: This function expects pre-escaped text.
|
||||
// It will unescape URLs if there are any.
|
||||
public static function linkify(string $text, bool $new_window = true): string {
|
||||
$link_attrs = $new_window ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
|
||||
// convert URLs to links
|
||||
$safe = preg_replace_callback(
|
||||
return preg_replace_callback(
|
||||
'~(https?://[^\s<>"\'()]+)~i',
|
||||
fn($matches) => '<a href="' . htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8') . '"' . $link_attrs . '>' . $matches[1] . '</a>',
|
||||
$safe
|
||||
);
|
||||
function($matches) use ($link_attrs) {
|
||||
$escaped_url = rtrim($matches[1], '.,!?;:)]}>');
|
||||
$clean_url = html_entity_decode($escaped_url, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return $safe;
|
||||
}
|
||||
return '<a href="' . $clean_url . '"' . $link_attrs . '>' . $escaped_url . '</a>';
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// For relative time display, compare the stored time to the current time
|
||||
// and display it as "X seconds/minutes/hours/days etc." ago
|
||||
|
@ -8,8 +8,8 @@ class HomeView {
|
||||
<div class="tick-feed">
|
||||
<?php foreach ($ticks as $tick): ?>
|
||||
<article class="tick">
|
||||
<div class="tick-time"><?= htmlspecialchars(Util::relative_time($tick['timestamp'])) ?></div>
|
||||
<span class="tick-text"><?= Util::escape_and_linkify($tick['tick']) ?></span>
|
||||
<div class="tick-time"><?= Util::escape_html(Util::relative_time($tick['timestamp'])) ?></div>
|
||||
<span class="tick-text"><?= Util::linkify(Util::escape_html($tick['tick'])) ?></span>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
@ -1,45 +1,49 @@
|
||||
<?php /** @var ConfigModel $config */ ?>
|
||||
<?php /** @var array $ticks */ ?>
|
||||
<?php
|
||||
$siteTitle = htmlspecialchars($config->siteTitle);
|
||||
$siteUrl = htmlspecialchars($config->baseUrl);
|
||||
$basePath = htmlspecialchars($config->basePath);
|
||||
$feedTitle = Util::escape_xml("$config->siteTitle Atom Feed");
|
||||
$siteUrl = Util::escape_xml($config->baseUrl . $config->basePath);
|
||||
$feedUrl = Util::escape_xml($config->baseUrl . $config->basePath . 'feed/atom');
|
||||
$updated = date(DATE_ATOM, strtotime($ticks[0]['timestamp'] ?? 'now'));
|
||||
|
||||
header('Content-Type: application/atom+xml; charset=utf-8');
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
||||
?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title><?= "$siteTitle Atom Feed" ?></title>
|
||||
<title><?php echo $feedTitle ?></title>
|
||||
<link rel="self"
|
||||
type="application/atom+xml"
|
||||
title="<?php echo htmlspecialchars($config->siteTitle) ?> Atom Feed"
|
||||
href="<?php echo htmlspecialchars($siteUrl . $basePath) ?>feed/atom" />
|
||||
<link rel="alternate" href="<?= $siteUrl ?>"/>
|
||||
<updated><?= $updated ?></updated>
|
||||
<id><?= $siteUrl . $basePath ?></id>
|
||||
title="<?php echo $feedTitle ?>"
|
||||
href="<?php echo $feedUrl ?>" />
|
||||
<link rel="alternate" href="<?php echo $siteUrl ?>"/>
|
||||
<updated><?php echo $updated ?></updated>
|
||||
<id><?php echo $siteUrl ?></id>
|
||||
<author>
|
||||
<name><?= $siteTitle ?></name>
|
||||
</author>
|
||||
<?php foreach ($ticks as $tick):
|
||||
// decompose the tick timestamp into the date/time parts
|
||||
[$date, $time] = explode(' ', $tick['timestamp']);
|
||||
$dateParts = explode('-', $date);
|
||||
$timeParts = explode(':', $time);
|
||||
|
||||
$dateParts = explode('-', $date);
|
||||
[$year, $month, $day] = $dateParts;
|
||||
|
||||
$timeParts = explode(':', $time);
|
||||
[$hour, $minute, $second] = $timeParts;
|
||||
|
||||
$tickPath = "$year/$month/$day/$hour/$minute/$second";
|
||||
$tickUrl = htmlspecialchars($siteUrl . $basePath . "tick/$tickPath");
|
||||
// build the tick entry components
|
||||
$tickPath = "tick/$year/$month/$day/$hour/$minute/$second";
|
||||
$tickUrl = Util::escape_xml($siteUrl . $basePath . $tickPath);
|
||||
$tickTime = date(DATE_ATOM, strtotime($tick['timestamp']));
|
||||
$tickText = htmlspecialchars($tick['tick']);
|
||||
$tickTitle = Util::escape_xml($tick['tick']);
|
||||
$tickContent = Util::linkify($tickTitle);
|
||||
?>
|
||||
<entry>
|
||||
<title><?= $tickText ?></title>
|
||||
<title><?= $tickTitle ?></title>
|
||||
<link href="<?= $tickUrl ?>"/>
|
||||
<id><?= $tickUrl ?></id>
|
||||
<updated><?= $tickTime ?></updated>
|
||||
<content type="html"><?= $tickText ?></content>
|
||||
<content type="html"><?= $tickContent ?></content>
|
||||
</entry>
|
||||
<?php endforeach; ?>
|
||||
</feed>
|
||||
|
@ -4,34 +4,43 @@
|
||||
// Need to have a little php here because the starting xml tag
|
||||
// will mess up the PHP parser.
|
||||
// TODO - I think short php tags can be disabled to prevent that.
|
||||
|
||||
header('Content-Type: application/rss+xml; charset=utf-8');
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||
?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title><?php echo htmlspecialchars($config->siteTitle, ENT_XML1, 'UTF-8') ?> RSS Feed</title>
|
||||
<link><?php echo htmlspecialchars($config->baseUrl . $config->basePath, ENT_XML1, 'UTF-8')?></link>
|
||||
<atom:link href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath, ENT_XML1, 'UTF-8')?>feed/rss" rel="self" type="application/rss+xml" />
|
||||
<description><?php echo htmlspecialchars($config->siteDescription, ENT_XML1, 'UTF-8') ?></description>
|
||||
<title><?php echo Util::escape_xml($config->siteTitle . 'RSS Feed') ?></title>
|
||||
<link><?php echo Util::escape_xml($config->baseUrl . $config->basePath)?></link>
|
||||
<atom:link href="<?php echo Util::escape_xml($config->baseUrl . $config->basePath. 'feed/rss')?>"
|
||||
rel="self"
|
||||
type="application/rss+xml" />
|
||||
<description><?php echo Util::escape_xml($config->siteDescription) ?></description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
|
||||
<?php foreach ($ticks as $tick):
|
||||
// decompose the tick timestamp into the date/time parts
|
||||
[$date, $time] = explode(' ', $tick['timestamp']);
|
||||
$dateParts = explode('-', $date);
|
||||
$timeParts = explode(':', $time);
|
||||
|
||||
$dateParts = explode('-', $date);
|
||||
[$year, $month, $day] = $dateParts;
|
||||
|
||||
$timeParts = explode(':', $time);
|
||||
[$hour, $minute, $second] = $timeParts;
|
||||
|
||||
$tickPath = "$year/$month/$day/$hour/$minute/$second";
|
||||
$tickUrl = $config->baseUrl . $config->basePath . $tickPath;
|
||||
// build the tick entry components
|
||||
$tickPath = "tick/$year/$month/$day/$hour/$minute/$second";
|
||||
$tickUrl = Util::escape_xml($config->baseUrl . $config->basePath . $tickPath);
|
||||
$tickDate = date(DATE_RSS, strtotime($tick['timestamp']));
|
||||
$tickTitle = Util::escape_xml($tick['tick']);
|
||||
$tickDescription = Util::linkify($tickTitle);
|
||||
?>
|
||||
<item>
|
||||
<title><?php echo htmlspecialchars($tick['tick'], ENT_XML1, 'UTF-8'); ?></title>
|
||||
<link><?php echo htmlspecialchars($config->baseUrl . $config->basePath . "tick/$tickPath", ENT_XML1, 'UTF-8'); ?></link>
|
||||
<description><?php echo Util::escape_and_linkify($tick['tick'], ENT_XML1, false); ?></description>
|
||||
<pubDate><?php echo date(DATE_RSS, strtotime($tick['timestamp'])); ?></pubDate>
|
||||
<guid><?php echo htmlspecialchars($tickUrl, ENT_XML1, 'UTF-8'); ?></guid>
|
||||
<title><?php echo $tickTitle ?></title>
|
||||
<link><?php echo $tickUrl; ?></link>
|
||||
<description><?php echo $tickDescription; ?></description>
|
||||
<pubDate><?php echo $tickDate; ?></pubDate>
|
||||
<guid><?php echo $tickUrl; ?></guid>
|
||||
</item>
|
||||
<?php endforeach; ?>
|
||||
</channel>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<p>About: <?= $user->about ?></p>
|
||||
<?php endif ?>
|
||||
<?php if (!empty($user->website)): ?>
|
||||
<p>Website: <?= Util::escape_and_linkify($user->website) ?></p>
|
||||
<p>Website: <?= Util::linkify(Util::escape_html($user->website)) ?></p>
|
||||
<?php endif ?>
|
||||
<?php if (!empty($user->mood) || Session::isLoggedIn()): ?>
|
||||
<div class="profile-row">
|
||||
@ -27,7 +27,7 @@
|
||||
<hr/>
|
||||
<div class="profile-row">
|
||||
<form class="tick-form" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||
<textarea name="tick" placeholder="What's ticking?" rows="3"></textarea>
|
||||
<button type="submit" class="submit-btn">Tick</button>
|
||||
</form>
|
||||
|
@ -2,4 +2,4 @@
|
||||
<?php /** @var Date $tickTime */ ?>
|
||||
<?php /** @var string $tick */ ?>
|
||||
<h1>Tick from <?= $tickTime->format('Y-m-d H:i:s'); ?></h1>
|
||||
<p><?= Util::escape_and_linkify($tick) ?></p>
|
||||
<p><?= Util::linkify(Util::escape_html($tick)) ?></p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user