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