innhold/content/kontakt/01-kontaktskjema.php
Ruben 7e44e7e132 Add CSRF protection and rate limiting to contact form
Improve contact form styling with dedicated CSS file Move contact form
styles from base.css to separate file Add security measures to custom
directory with .htaccess Update honeypot field styling and
implementation
2025-11-04 22:33:14 +01:00

277 lines
11 KiB
PHP

<?php
// Contact form processing with spam prevention
$formSubmitted = false;
$formSuccess = false;
$formErrors = [];
$formData = ['name' => '', 'email' => '', 'message' => ''];
// Start session for CSRF token and rate limiting
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Generate CSRF token if not exists
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Process form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['contact_form_submit'])) {
$formSubmitted = true;
// Security: CSRF Token Validation
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
$formErrors[] = 'Ugyldig sikkerhetskode. Vennligst prøv igjen.';
}
// Spam Prevention 1: Honeypot field (should be empty)
if (!empty($_POST['website'])) {
$formErrors[] = 'Spam detected.';
}
// Spam Prevention 2: Time-based check (form must be visible for at least 3 seconds)
$formStartTime = isset($_POST['form_start_time']) ? (int)$_POST['form_start_time'] : 0;
$timeDiff = time() - $formStartTime;
if ($timeDiff < 3) {
$formErrors[] = 'Form submitted too quickly.';
}
// Spam Prevention 3: Referrer check (only if referrer is present and clearly from different domain)
if (!empty($_SERVER['HTTP_REFERER'])) {
$referrer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
$currentHost = $_SERVER['HTTP_HOST'];
// Only block if referrer exists and doesn't match (allows empty referrer for privacy browsers)
if ($referrer && $referrer !== $currentHost && $referrer !== 'localhost') {
$formErrors[] = 'Invalid form submission.';
}
}
// Spam Prevention 4: Rate limiting (session-based)
$lastSubmitTime = isset($_SESSION['last_contact_submit']) ? $_SESSION['last_contact_submit'] : 0;
if (time() - $lastSubmitTime < 60) {
$formErrors[] = 'Vennligst vent litt før du sender inn igjen.';
}
// Get and sanitize form data
$formData['name'] = trim($_POST['name'] ?? '');
$formData['email'] = trim($_POST['email'] ?? '');
// Normalize line endings in message (convert \r\n to \n)
$formData['message'] = trim(str_replace("\r\n", "\n", $_POST['message'] ?? ''));
// Validation
if (empty($formData['name'])) {
$formErrors[] = 'Vennligst oppgi navn.';
} elseif (strlen($formData['name']) > 100) {
$formErrors[] = 'Navnet er for langt.';
}
if (empty($formData['email'])) {
$formErrors[] = 'Vennligst oppgi e-postadresse.';
} elseif (!filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
$formErrors[] = 'Ugyldig e-postadresse.';
} elseif (strlen($formData['email']) > 100) {
$formErrors[] = 'E-postadressen er for lang.';
}
if (empty($formData['message'])) {
$formErrors[] = 'Vennligst skriv en melding.';
} elseif (strlen($formData['message']) < 10) {
$formErrors[] = 'Meldingen er for kort (minimum 10 tegn).';
} elseif (strlen($formData['message']) > 5000) {
$formErrors[] = 'Meldingen er for lang (maksimum 5000 tegn).';
}
// Spam Prevention 5: Check for suspicious patterns
$spamPatterns = [
'/\[url=/i',
'/\[link=/i',
'/<a href=/i',
'/viagra|cialis|casino|poker|lottery/i',
'/http.*http.*http/i', // Multiple URLs
];
$fullText = $formData['name'] . ' ' . $formData['email'] . ' ' . $formData['message'];
foreach ($spamPatterns as $pattern) {
if (preg_match($pattern, $fullText)) {
$formErrors[] = 'Meldingen inneholder ikke tillatt innhold.';
break;
}
}
// If no errors, send email
if (empty($formErrors)) {
// Load SMTP configuration
$smtpConfig = require __DIR__ . '/../../custom/smtp-config.php';
// Prepare email body
$emailBody = "Ny henvendelse fra kontaktskjemaet på stopplidelsen.no\n\n";
$emailBody .= "Navn: " . $formData['name'] . "\n";
$emailBody .= "E-post: " . $formData['email'] . "\n";
$emailBody .= "IP-adresse: " . $_SERVER['REMOTE_ADDR'] . "\n";
$emailBody .= "Tidspunkt: " . date('Y-m-d H:i:s') . "\n\n";
$emailBody .= "Melding:\n" . $formData['message'] . "\n";
$mailSent = false;
// Try PHPMailer.Lite if SMTP is configured
if ($smtpConfig['enabled']) {
// Pre-flight check: Test SMTP connection before attempting to send
$smtpConnectable = false;
$fp = @fsockopen($smtpConfig['host'], $smtpConfig['port'], $errno, $errstr, 10);
if ($fp) {
// Successfully connected
$smtpConnectable = true;
fclose($fp);
} else {
error_log("SMTP Pre-flight check failed: Cannot connect to {$smtpConfig['host']}:{$smtpConfig['port']} - Error: $errno - $errstr");
$formErrors[] = 'Det oppstod en feil ved sending av meldingen. Vennligst prøv igjen senere.';
}
if ($smtpConnectable) {
try {
require_once __DIR__ . '/../../custom/vendor/PHPMailer.Lite.php';
$mail = new \codeworxtech\PHPMailerLite\PHPMailerLite();
// Enable debug output in development
// $mail->debug = 1; // Uncomment to see SMTP debug output
$mail->SetSMTPhost($smtpConfig['host']);
$mail->SetSMTPport($smtpConfig['port']);
$mail->SetSMTPuser($smtpConfig['username']);
$mail->SetSMTPpass($smtpConfig['password']);
$mail->SetSender([$smtpConfig['from_email'] => $smtpConfig['from_name']]);
$mail->AddRecipient([$smtpConfig['to_email'] => $smtpConfig['to_name']]);
$mail->AddReplyTo([$formData['email'] => $formData['name']]);
$mail->SetSubject('Ny henvendelse fra kontaktskjema');
$mail->SetBodyText($emailBody);
// Capture any output from PHPMailer.Lite (it might exit() on error)
ob_start();
$mailSent = @$mail->Send('smtp');
$smtpOutput = ob_get_clean();
// Check if there was an error in the output
if (!$mailSent || stripos($smtpOutput, 'error') !== false || stripos($smtpOutput, '&#10007;') !== false) {
$mailSent = false;
error_log("SMTP Send failed. Output: " . strip_tags($smtpOutput));
$formErrors[] = 'Det oppstod en feil ved sending av meldingen. Vennligst prøv igjen senere.';
}
} catch (\Exception $e) {
$mailSent = false;
error_log("PHPMailer Exception: " . $e->getMessage());
$formErrors[] = 'Det oppstod en feil ved sending av meldingen. Vennligst prøv igjen senere.';
}
}
} else {
// Fallback to native mail() function
$headers = "From: kontaktskjema@stopplidelsen.no\r\n";
$headers .= "Reply-To: " . $formData['email'] . "\r\n";
$headers .= "X-Mailer: PHP/" . phpversion() . "\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
$mailSent = mail('ruben@stopplidelsen.no', 'Ny henvendelse fra kontaktskjema', $emailBody, $headers);
}
if ($mailSent) {
$formSuccess = true;
$_SESSION['last_contact_submit'] = time();
// Clear form data on success
$formData = ['name' => '', 'email' => '', 'message' => ''];
} else {
if (empty($formErrors)) {
$formErrors[] = 'Det oppstod en feil ved sending av meldingen. Vennligst prøv igjen senere.';
}
}
}
}
// Generate form start time token
$currentTime = time();
?>
<article class="contain">
<h2>Kontaktskjema</h2>
<p>Har du spørsmål, innspill eller ønsker å delta i arbeidet vårt? Fyll ut skjemaet nedenfor! Men husk at det kan ta tid før du får svar, vi er ikke så mange og driver alt på frivillig basis.</p>
<section class="contact-form">
<?php if ($formSuccess): ?>
<div class="form-success">
<p><strong>Takk for din henvendelse!</strong></p>
<p>Vi har mottatt meldingen din og vil svare så snart som mulig.</p>
</div>
<?php endif; ?>
<?php if (!empty($formErrors)): ?>
<div class="form-errors">
<p><strong>Vennligst rett opp følgende:</strong></p>
<ul>
<?php foreach ($formErrors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if (!$formSuccess): ?>
<form method="post" action="<?= htmlspecialchars($_SERVER['REQUEST_URI']) ?>" class="contact-form-inner">
<!-- Honeypot field (hidden from users, bots will fill it) -->
<div class="hp-field" aria-hidden="true">
<label for="website">Website</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off">
</div>
<!-- CSRF Token -->
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<!-- Time-based token -->
<input type="hidden" name="form_start_time" value="<?= $currentTime ?>">
<div class="form-group">
<label for="contact_name">Navn <span class="required">*</span></label>
<input
type="text"
id="contact_name"
name="name"
value="<?= htmlspecialchars($formData['name']) ?>"
required
maxlength="100"
>
</div>
<div class="form-group">
<label for="contact_email">E-postadresse <span class="required">*</span></label>
<input
type="email"
id="contact_email"
name="email"
value="<?= htmlspecialchars($formData['email']) ?>"
required
maxlength="100"
>
</div>
<div class="form-group">
<label for="contact_message">Melding <span class="required">*</span></label>
<textarea
id="contact_message"
name="message"
rows="8"
required
minlength="10"
maxlength="5000"
><?= htmlspecialchars($formData['message']) ?></textarea>
<small>Minimum 10 tegn, maksimum 5000 tegn.</small>
</div>
<div class="form-group">
<button type="submit" name="contact_form_submit" class="button">Send melding</button>
</div>
</form>
<?php endif; ?>
</section>
</article>