175 lines
5.1 KiB
PHP
175 lines
5.1 KiB
PHP
<?php
|
|
session_start();
|
|
require __DIR__ . '/config.php';
|
|
|
|
$postsFile = __DIR__ . '/data/posts.json';
|
|
|
|
if (!isset($_SESSION['csrf'])) {
|
|
$_SESSION['csrf'] = bin2hex(random_bytes(16));
|
|
}
|
|
|
|
function load_posts($file) {
|
|
$raw = @file_get_contents($file);
|
|
$arr = json_decode($raw ?: '[]', true);
|
|
return is_array($arr) ? $arr : [];
|
|
}
|
|
function save_posts($file, $posts) {
|
|
$tmp = $file . '.tmp';
|
|
file_put_contents($tmp, json_encode($posts, JSON_PRETTY_PRINT));
|
|
rename($tmp, $file);
|
|
}
|
|
|
|
$msg = '';
|
|
$err = '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
|
|
$err = "Erreur CSRF.";
|
|
} else {
|
|
// Rate limit par IP
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
|
$lastKey = 'last_post_' . md5($ip);
|
|
$now = time();
|
|
$last = $_SESSION[$lastKey] ?? 0;
|
|
if ($now - $last < RATE_LIMIT_SECONDS) {
|
|
$err = "Trop rapide : 1 post par minute maximum.";
|
|
} else {
|
|
$text = trim($_POST['text'] ?? '');
|
|
if ($text === '' && empty($_FILES['image']['name'])) {
|
|
$err = "Il faut un message ou une image.";
|
|
} else {
|
|
$imagePath = '';
|
|
if (!empty($_FILES['image']['name'])) {
|
|
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
|
|
$err = "Erreur upload.";
|
|
} else if ($_FILES['image']['size'] > MAX_UPLOAD_BYTES) {
|
|
$err = "Image trop grosse (max ".MAX_UPLOAD_BYTES." bytes).";
|
|
} else {
|
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
|
$mime = $finfo->file($_FILES['image']['tmp_name']);
|
|
$allowed = [
|
|
'image/png' => 'png',
|
|
'image/jpeg' => 'jpg',
|
|
];
|
|
if (!isset($allowed[$mime])) {
|
|
$err = "Format interdit. Autorisé: PNG/JPEG.";
|
|
} else {
|
|
$ext = $allowed[$mime];
|
|
$name = bin2hex(random_bytes(8)) . '.' . $ext;
|
|
$dest = __DIR__ . '/uploads/' . $name;
|
|
if (!move_uploaded_file($_FILES['image']['tmp_name'], $dest)) {
|
|
$err = "Impossible de sauvegarder l'image.";
|
|
} else {
|
|
$imagePath = '/uploads/' . $name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$err) {
|
|
$posts = load_posts($postsFile);
|
|
$posts[] = [
|
|
'time' => date('c'),
|
|
'ip' => $ip,
|
|
'text' => $text,
|
|
'img' => $imagePath
|
|
];
|
|
// plus récents en haut => on affichera en reverse
|
|
save_posts($postsFile, $posts);
|
|
$_SESSION[$lastKey] = $now;
|
|
$msg = "Post publié.";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$posts = load_posts($postsFile);
|
|
$posts = array_reverse($posts); // plus récents en haut
|
|
?>
|
|
<!doctype html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>DevWeb Forum</title>
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<div class="container">
|
|
|
|
<div class="nav">
|
|
<div class="brand">
|
|
<span class="dot"></span>
|
|
Mini-forum
|
|
</div>
|
|
|
|
<div>
|
|
<a href="/">Accueil</a>
|
|
<a href="/login.php">Login</a>
|
|
<a href="/flag.php">Flag</a>
|
|
<?php if (!empty($_SESSION['is_admin'])): ?>
|
|
<a href="/logout.php">Logout</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card header">
|
|
<h1>Mini-forum</h1>
|
|
|
|
<?php if (!empty($msg)): ?>
|
|
<div class="alert"><?php echo htmlspecialchars($msg); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($err)): ?>
|
|
<div class="alert danger"><?php echo htmlspecialchars($err); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($_SESSION['csrf']); ?>">
|
|
|
|
<div>
|
|
<label for="text">Commentaire</label>
|
|
<textarea id="text" name="text" placeholder="Ton message..."></textarea>
|
|
<div class="helper">Les posts récents apparaissent en premier.</div>
|
|
</div>
|
|
|
|
<div class="file">
|
|
<label for="image">Image (PNG/JPEG, max <?php echo (int)(MAX_UPLOAD_BYTES/1024/1024); ?> MB)</label>
|
|
<input id="image" type="file" name="image" accept=".png,.jpg,.jpeg,image/png,image/jpeg">
|
|
<div class="helper">Taille max côté serveur : <?php echo (int)(MAX_UPLOAD_BYTES/1024/1024); ?> MB.</div>
|
|
</div>
|
|
|
|
<button class="btn primary" type="submit">Publier</button>
|
|
</form>
|
|
</div>
|
|
|
|
<h2>Posts</h2>
|
|
|
|
<div class="posts">
|
|
<?php foreach ($posts as $p): ?>
|
|
<div class="post">
|
|
<div class="meta">
|
|
<span class="badge">📅 <?php echo htmlspecialchars($p['time']); ?></span>
|
|
<span class="badge">🌐 IP <?php echo htmlspecialchars($p['ip']); ?></span>
|
|
</div>
|
|
|
|
<?php if (!empty($p['text'])): ?>
|
|
<div class="content"><?php echo nl2br(htmlspecialchars($p['text'])); ?></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($p['img'])): ?>
|
|
<img src="<?php echo htmlspecialchars($p['img']); ?>" alt="Upload">
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<div class="footer">CTF • dev.h.ctf.arrobe.fr</div>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|