post.php 是論壇的發文介面,提供標題、內容輸入欄位,整合 JavaScript 前端驗證(必填檢查、防重複送出),未登入使用者需輸入帳號密碼,送出後導向 okey.php 處理。
<?php
include "linkfox.inc.php";
include "info.inc.php";
$tem = $_COOKIE['FlyFoxNet'] ?? '';
$temp = explode('|', $tem);
$cookiem = $temp[0] ?? '';
$useinfo = cuser($cookiem, $action);
$usename = ($useinfo[2] === '游客') ? 1 : 0; // 1=需驗證身分, 0=已登入
?>
<script>
var submitcount = 0;
function checkSubmit() {
if (submitcount === 0) {
submitcount++;
return true;
} else {
alert('帖子已成功提交了,別急,等等!');
return false;
}
}
function check_com() {
if (document.form.title.value.length === 0) {
submitcount--;
alert('主題不能為空!快填上吧!');
return false;
}
if (document.form.message.value.length === 0) {
submitcount--;
alert('OH!SORRY!加貼內容不能為空!');
return false;
}
return true;
}
</script>
缺陷:submitcount 為全域變數,頁面重新整理即失效;無法防禦繞過 JS 的惡意請求。伺服端必須實作 CSRF Token 與冪等性檢查。
<form name="form" method="post" onSubmit="return check_com()" ACTION="okey.php">
<?php if ($usename == 1): ?>
用戶名:<input type="text" name="username" maxlength="8" size="20">
密 碼:<input type="password" name="userpass" size="20" maxlength="8">
<?php endif; ?>
主 題:<input type="text" name="title" size="40" maxlength="20"><br>
帖子內容<br>
<textarea name="message" wrap="VIRTUAL" cols="75" rows="10"></textarea><br>
<input type="submit" value="好了!提交" onClick="return checkSubmit()">
<input type="reset" value="擦了!重寫">
</form>
大量使用 style="BACKGROUND-COLOR: #ffffff; BORDER-BOTTOM: #000000 1px dashed;..." 內嵌樣式,建議抽離至 CSS 檔案,並使用類別管理。
cols/rows 固定,不利行動裝置。alert() 阻斷流程,現代建議改用非阻斷的 Toast/Inline 提示。<?php
// post_handler.php
session_start();
require_once 'db.php';
require_once 'csrf.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Method Not Allowed');
}
if (!validate_csrf_token($_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
$title = trim($_POST['title'] ?? '');
$content = trim($_POST['message'] ?? '');
$errors = [];
if ($title === '') $errors[] = '主題不可為空';
if (mb_strlen($title) > 100) $errors[] = '主題不得超過 100 字';
if ($content === '') $errors[] = '內容不可為空';
if (mb_strlen($content) > 50000) $errors[] = '內容過長';
if ($errors) {
// 回傳錯誤給前端(JSON 或重導帶參數)
$_SESSION['flash_errors'] = $errors;
header('Location: post.php');
exit;
}
// 使用者身分解析
$userId = $_SESSION['user_id'] ?? null;
$isGuest = $userId === null;
// 防重複發文:同用戶、同標題、同內容、短時間內
$stmt = $pdo->prepare("
SELECT 1 FROM topics
WHERE user_id = ? AND title = ? AND content = ?
AND created_at > DATE_SUB(NOW(), INTERVAL 30 SECOND)
");
$stmt->execute([$userId, $title, $content]);
if ($stmt->fetchColumn()) {
$_SESSION['flash_errors'] = ['請勿重複發文'];
header('Location: post.php');
exit;
}
// 寫入資料庫
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("
INSERT INTO topics (user_id, title, content, ip, is_guest, guest_name)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$userId,
$title,
$content,
$_SERVER['REMOTE_ADDR'],
$isGuest ? 1 : 0,
$isGuest ? ($_POST['guest_name'] ?? '匿名') : null
]);
$pdo->commit();
header('Location: topic.php?id=' . $pdo->lastInsertId());
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
?>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
const form = reactive({
title: '',
content: '',
username: '',
password: '',
guestName: ''
})
const errors = ref([])
const submitting = ref(false)
const router = useRouter()
const submit = async () => {
errors.value = []
if (!form.title.trim()) errors.value.push('主題不可為空')
if (!form.content.trim()) errors.value.push('內容不可為空')
if (errors.value.length) return
submitting.value = true
try {
const formData = new FormData()
formData.append('title', form.title)
formData.append('message', form.content)
formData.append('csrf_token', getCsrfToken()) // 從 meta tag 或 cookie 取得
if (isGuest.value) {
formData.append('username', form.username)
formData.append('userpass', form.password)
}
const res = await fetch('post_handler.php', { method: 'POST', body: formData })
if (res.redirected) {
router.push(res.url)
} else {
const data = await res.json()
errors.value = data.errors || ['發文失敗']
}
} finally {
submitting.value = false
}
}
</script>
<template>
<form @submit.prevent="submit" class="post-form">
<div v-if="errors.length" class="errors">
<ul><li v-for="e in errors">{{ e }}</li></ul>
</div>
<div v-if="isGuest" class="auth-fields">
<input v-model="form.username" placeholder="用戶名" maxlength="20">
<input v-model="form.password" type="password" placeholder="密碼" maxlength="20">
</div>
<input v-model="form.title" placeholder="主題 (最多 100 字)" maxlength="100">
<textarea v-model="form.content" placeholder="內容" rows="12"></textarea>
<button :disabled="submitting" type="submit">
{{ submitting ? '發文中...' : '發布主題' }}
</button>
</form>
</template>
post.php 實作了基本的發文流程與前端攔截,但缺乏伺服端驗證、CSRF 保護、HTTPS 強制等現代 Web 安全基線。重構時應採用「前端體驗優化、後端安全把關」的分層策略,並引入 CSRF Token、速率限制、內容過濾(XSS 防護)、冪等性鍵等機制,確保發文功能在高併發與惡意攻擊下依然穩健。