Files
0xa0.dev/guestbook.php
T
2026-07-22 11:33:23 +02:00

114 lines
3.4 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/inc/moderation.php';
@mkdir(DATA_DIR, 0755, true);
$db = new PDO('sqlite:' . DATA_DIR . '/guestbook.db');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec('CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
msg TEXT NOT NULL,
ts INTEGER NOT NULL
)');
$db->exec('CREATE TABLE IF NOT EXISTS ratelimit (
ip_hash TEXT PRIMARY KEY,
last_ts INTEGER NOT NULL
)');
$isHtmx = isset($_SERVER['HTTP_HX_REQUEST']);
$notice = '';
function client_ip(): string {
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
if ($xff !== '') {
return trim(explode(',', $xff)[0]);
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$msg = trim($_POST['msg'] ?? '');
$trap = trim($_POST['url'] ?? '');
$ok = false;
if ($trap !== '') {
$ok = true;
} elseif ($name === '' || $msg === '') {
$notice = 'name and message are required.';
} elseif (gb_has_link($msg)) {
$notice = 'links aren\'t allowed.';
} else {
$ipHash = hash('sha256', client_ip() . '|guestbook');
$now = time();
$window = 30;
$stmt = $db->prepare('SELECT last_ts FROM ratelimit WHERE ip_hash = ?');
$stmt->execute([$ipHash]);
$last = (int) $stmt->fetchColumn();
if ($last && $now - $last < $window) {
$notice = 'you\'re posting too fast — wait a moment.';
} else {
$ins = $db->prepare('INSERT INTO entries (name, msg, ts) VALUES (?, ?, ?)');
$ins->execute([
gb_censor(mb_substr($name, 0, 40)),
gb_censor(mb_substr($msg, 0, 500)),
$now,
]);
$db->prepare('INSERT INTO ratelimit (ip_hash, last_ts) VALUES (?, ?)
ON CONFLICT(ip_hash) DO UPDATE SET last_ts = excluded.last_ts')
->execute([$ipHash, $now]);
$ok = true;
}
}
if (!$isHtmx && $ok) {
header('Location: /guestbook.php');
exit;
}
}
$rows = $db->query('SELECT name, msg, ts FROM entries ORDER BY id DESC LIMIT 50');
function render_entries(iterable $rows): void {
foreach ($rows as $r) { ?>
<div class="entry">
<b><?= htmlspecialchars($r['name']) ?></b>
<span class="date"><?= date('Y-m-d', $r['ts']) ?></span>
<p><?= nl2br(htmlspecialchars($r['msg'])) ?></p>
</div>
<?php }
}
if ($isHtmx) {
if ($notice !== '') {
echo '<p class="gb-notice">' . htmlspecialchars($notice) . '</p>';
}
render_entries($rows);
exit;
}
require __DIR__ . '/inc/header.php';
?>
<h1>guestbook</h1>
<?php if ($notice !== ''): ?>
<p class="gb-notice"><?= htmlspecialchars($notice) ?></p>
<?php endif; ?>
<form hx-post="/guestbook.php" hx-target="#entries" hx-swap="innerHTML"
method="post" action="/guestbook.php">
<input name="name" placeholder="name" maxlength="40" required>
<textarea name="msg" placeholder="say something…" maxlength="500" required></textarea>
<input name="url" tabindex="-1" autocomplete="off" aria-hidden="true"
style="position:absolute;left:-9999px" placeholder="leave this empty">
<button type="submit">sign</button>
</form>
<div id="entries">
<?php render_entries($rows); ?>
</div>
<?php require __DIR__ . '/inc/footer.php'; ?>