···
3
+
const COOKIE_LIFETIME = 60 * 60 * 24 * 365; // 1 year
4
+
const SITE_URL = 'https://tz.rita.moe';
6
+
// Set security headers
7
+
$nonce = bin2hex(random_bytes(16));
8
+
header("Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-$nonce'; img-src 'self'; style-src 'self'; form-action 'self';");
9
+
header('X-Content-Type-Options: nosniff');
10
+
header('X-Frame-Options: DENY');
11
+
header('X-XSS-Protection: 1; mode=block');
function getOffset($tz) {
$tz = new DateTime('now', new DateTimeZone($tz));
$offset = $tz->format('T');
···
function setUserCookie($name, $value) {
14
-
setcookie($name, $value, time() + 60 * 60 * 24 * 365, '/');
25
+
setcookie($name, $value, time() + COOKIE_LIFETIME, '/');
$_COOKIE[$name] = $value;
19
-
'America/Los_Angeles',
30
+
'America/Los_Angeles', // PST/PDT
31
+
'America/New_York', // EST/EDT
32
+
'Europe/London', // BST/GMT
33
+
'Europe/Paris', // CEST/CET
34
+
'Asia/Dubai', // UTC+04 "UAE"
35
+
'Asia/Jakarta', // WIB
36
+
'Asia/Tokyo', // JST
37
+
'Australia/Sydney', // AEST/AEDT
41
+
$allTZ = timezone_identifiers_list(DateTimeZone::ALL);
// User sets timezone cookie
31
-
if (isset($_POST['user-tz']) &&
32
-
in_array($_POST['user-tz'], timezone_identifiers_list(DateTimeZone::ALL))) {
44
+
if (isset($_POST['user-tz']) && in_array($_POST['user-tz'], $allTZ)) {
setUserCookie('user-tz', $_POST['user-tz']);
···
47
-
if (isset($_POST['datetime']) && isset($_POST['timezone'])) {
48
-
// -- Redirect to submitted date
49
-
$postTZ = $_POST['timezone'];
60
+
if (isset($_POST['datetime']) && isset($_POST['timezone'])) {
61
+
// -- Redirect to submitted date
62
+
$postTZ = $_POST['timezone'];
51
-
// Handle if timezone is an offset
52
-
if (is_numeric($_POST['timezone'])) {
53
-
$isNeg = str_starts_with($postTZ, '-');
54
-
$h = str_pad(abs(intdiv($postTZ, 60)), 2, '0', STR_PAD_LEFT);
55
-
$m = str_pad($postTZ % 60, 2, '0', STR_PAD_LEFT);
56
-
$postTZ = ($isNeg ? '-' : '+') . $h . $m;
64
+
// Handle if timezone is an offset
65
+
if (is_numeric($_POST['timezone'])) {
66
+
$isNeg = str_starts_with($postTZ, '-');
67
+
$h = str_pad(abs(intdiv($postTZ, 60)), 2, '0', STR_PAD_LEFT);
68
+
$m = str_pad($postTZ % 60, 2, '0', STR_PAD_LEFT);
69
+
$postTZ = ($isNeg ? '-' : '+') . $h . $m;
59
-
// Make our date object
60
-
$dateObj = new DateTime($_POST['datetime'], new DateTimeZone($postTZ));
72
+
// Make our date object
73
+
$dateObj = new DateTime($_POST['datetime'], new DateTimeZone($postTZ));
62
-
// Redirect, with the "+" replaced by "_"
63
-
header('Location: /' . str_replace('+', '_', $dateObj->format('c')));
65
-
} elseif ($_SERVER['REQUEST_URI'] !== '/') {
66
-
// Remove leading "/"
67
-
$req = substr($_SERVER['REQUEST_URI'], 1);
75
+
// Redirect, with the "+" replaced by "_"
76
+
header('Location: /' . str_replace('+', '_', $dateObj->format('c')));
78
+
} elseif ($_SERVER['REQUEST_URI'] !== '/') {
79
+
// Remove leading "/"
80
+
$req = substr($_SERVER['REQUEST_URI'], 1);
69
-
// -- Show date infos
70
-
// First check if date is following the format
71
-
$re = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\_|-]\d{2}:\d{2}$/';
72
-
if (!preg_match($re, $req)) {
73
-
http_response_code(404);
74
-
$error = 'Date is not valid, wrong format, or file not found.';
76
-
// Make our date object, convert back "_" to "+"
77
-
$dt = new DateTime(str_replace('_', '+', $req));
82
+
// -- Show date infos
83
+
// First check if date is following the format
84
+
$re = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\_|-]\d{2}:\d{2}$/';
85
+
if (!preg_match($re, $req)) {
86
+
http_response_code(404);
87
+
$error = 'Date is not valid, wrong format, or file not found.';
89
+
// Make our date object, convert back "_" to "+"
90
+
$dt = new DateTime(str_replace('_', '+', $req));
93
+
} catch (Exception $e) {
94
+
http_response_code(400);
95
+
$error = 'Invalid date format or date out of range.';
···
<meta name="viewport" content="width=device-width, initial-scale=1.0">
90
-
<title>Time Zones Are Hard<?= isset($req) ? ' - ' . str_replace('_', '+', $req) : '' ?></title>
107
+
<title>Time Zones Are Hard<?= isset($req) ? ' - ' . htmlspecialchars(str_replace('_', '+', $req), ENT_QUOTES, 'UTF-8') : '' ?></title>
<link rel="stylesheet" href="/css/styles.css">
<?php if (isset($dt)) { ?>
<meta name="robots" content="noindex,noarchive">
<script src="/js/dayjs.min.js"></script>
<script src="/js/relativeTime.js"></script>
113
+
<script nonce="<?= $nonce ?>">
dayjs.extend(window.dayjs_plugin_relativeTime)
···
Please select your local time zone <small>(or enable JavaScript)</small>:<br/>
131
-
<select required id="user-tz" name="user-tz">
148
+
<select required id="user-tz" name="user-tz" aria-label="Timezone selector">
<option selected disabled>(pick one)</option>
<optgroup label="Popular">
<?php foreach($popTZ as $tz) { ?>
···
141
-
<?php foreach(timezone_identifiers_list(DateTimeZone::ALL) as $tz) { ?>
158
+
<?php foreach($allTZ as $tz) { ?>
<option value="<?= $tz ?>">
<?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>)
···
149
-
<input type="checkbox" id="user-24" name="user-24"/>
166
+
<input type="checkbox" id="user-24" name="user-24" aria-label="Use 24-hour time format instead of 12-hour AM/PM"/>
<label for="user-24"><span>24h format</span></label>
<button type="submit">Submit</button>
···
178
-
<input type="hidden" id="url" value="https://tz.rita.moe<?= $_SERVER['REQUEST_URI'] ?>"/>
195
+
<input type="hidden" id="url" value="<?= htmlspecialchars(SITE_URL . $_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8') ?>"/>
198
+
<script nonce="<?= $nonce ?>">
// Make our day.js object
const djs = dayjs('<?= $dt->format('c') ?>')
···
if (navigator.canShare) {
// Use native navigator share
209
-
url: 'https://tz.rita.moe<?= $_SERVER['REQUEST_URI'] ?>'
226
+
url: '<?= htmlspecialchars(SITE_URL . $_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8') ?>'
} else if (navigator.clipboard) {
213
-
navigator.clipboard.writeText('https://tz.rita.moe<?= $_SERVER['REQUEST_URI'] ?>')
230
+
navigator.clipboard.writeText('<?= htmlspecialchars(SITE_URL . $_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8') ?>')
alert('URL copied to clipboard!')
···
alert('URL copied to clipboard!')
// Fallback to prompt with URL
238
-
prompt('Share this URL:', document.querySelector('#url').value)
255
+
prompt('Share this URL:', shareTextInput.value)
···
269
-
<?php foreach(timezone_identifiers_list(DateTimeZone::ALL) as $tz) { ?>
286
+
<?php foreach($allTZ as $tz) { ?>
<option value="<?= $tz ?>">
<?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>)
···
<button type="submit">Submit</button>
299
+
<script nonce="<?= $nonce ?>">
···
<?php if ($_COOKIE['has-js'] !== '1') { ?>
319
+
<script nonce="<?= $nonce ?>">
// Save on the document size if user has JavaScript
305
-
date.setTime(date.getTime() + (1000 * 60 * 60 * 24 * 365))
322
+
date.setTime(date.getTime() + (1000 * <?= COOKIE_LIFETIME ?>))
document.cookie = 'has-js=1; expires=' + date.toUTCString() + '; path=/'