1<?php
2class Token {
3
4 public static function generate(): void {
5 $_SESSION['token'] = bin2hex(random_bytes(32));
6 }
7
8 public static function create(): void {
9 if (!self::get()) {
10 self::generate();
11 }
12 echo '<input type="hidden" name="token" value="' . self::get() . '">';
13 }
14
15 public static function get(): string|false {
16 return $_SESSION['token'] ?? false;
17 }
18
19 public static function isValid(?string $post): bool {
20 if (!$post || !self::get()) {
21 return false;
22 }
23
24 $valid = hash_equals($_SESSION['token'], $post);
25
26 // 🔐 IMPORTANT: token usage unique
27 self::_reset();
28
29 return $valid;
30 }
31
32 protected static function _reset(): void {
33 unset($_SESSION['token']);
34 }
35}
36
37function znote_csrf_field(): string {
38 if (!Token::get()) {
39 Token::generate();
40 }
41
42 return '<input type="hidden" name="token" value="' . htmlspecialchars(Token::get(), ENT_QUOTES, 'UTF-8') . '">';
43}
44
45function znote_csrf_validate_post(): bool {
46 if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
47 return true;
48 }
49
50 $posted = $_POST['token'] ?? null;
51 $session = Token::get();
52 if (!is_string($posted) || $posted === '' || !is_string($session) || $session === '') {
53 return false;
54 }
55
56 $valid = hash_equals($session, $posted);
57 if ($valid) {
58 Token::generate();
59 }
60
61 return $valid;
62}
63
64function znote_csrf_protect_public_post(): void {
65 if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST' && !znote_csrf_validate_post()) {
66 http_response_code(400);
67 die('Invalid or expired form token. Please go back, refresh the page and try again.');
68 }
69
70 ob_start(static function (string $html): string {
71 if (stripos($html, '<form') === false || stripos($html, 'method') === false) {
72 return $html;
73 }
74
75 return preg_replace_callback(
76 '~<form\b(?=[^>]*\bmethod\s*=\s*["\']?post["\']?)[^>]*>~i',
77 static function (array $match): string {
78 if (stripos($match[0], 'name="token"') !== false || stripos($match[0], "name='token'") !== false) {
79 return $match[0];
80 }
81
82 return $match[0] . "\n" . znote_csrf_field();
83 },
84 $html
85 ) ?? $html;
86 });
87}
88?>
89