From: Danilo M. Date: Wed, 15 Apr 2026 13:58:26 +0000 (+0200) Subject: added contact form backend X-Git-Tag: release_22042026-1342~256 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=0f0fb848fc6f7e6c3333c6b4595aa8dcaff92068;p=danix.xyz-2.git added contact form backend --- diff --git a/static/api/composer.json b/static/api/composer.json new file mode 100644 index 0000000..078861f --- /dev/null +++ b/static/api/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpmailer/phpmailer": "^7.0" + } +} diff --git a/static/api/composer.lock b/static/api/composer.lock new file mode 100644 index 0000000..447fe18 --- /dev/null +++ b/static/api/composer.lock @@ -0,0 +1,101 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "826f515f5ef16946d3e3ee3e3205b25e", + "packages": [ + { + "name": "phpmailer/phpmailer", + "version": "v7.0.2", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088", + "reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "squizlabs/php_codesniffer": "^3.13.5", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example", + "ext-imap": "Needed to support advanced email address parsing according to RFC822", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.0.2" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2026-01-09T18:02:33+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/static/api/contact.php b/static/api/contact.php new file mode 100644 index 0000000..4689b99 --- /dev/null +++ b/static/api/contact.php @@ -0,0 +1,180 @@ + getenv('MAIL_RECIPIENT') ?: '', +// 'smtp_host' => getenv('MAIL_SMTP_HOST') ?: '', +// 'smtp_port' => (int)(getenv('MAIL_SMTP_PORT') ?: 587), +// 'smtp_secure' => getenv('MAIL_SMTP_SECURE') ?: 'tls', +// 'smtp_user' => getenv('MAIL_SMTP_USER') ?: '', +// 'smtp_pass' => getenv('MAIL_SMTP_PASS') ?: '', +// ]; + +$cfg = require dirname(__DIR__, 2) . '/mail-config.php'; + +if ($cfg['recipient'] === '' || $cfg['smtp_host'] === '' || $cfg['smtp_user'] === '') { + out(500, 'Mail not configured'); +} +// ────────────────────────────────────────────────────────────────────────────── + +// Only accept POST +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + out(405, 'Method not allowed'); +} + +// ── CSRF: verify request originates from our own domain ─────────────────────── +$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; +$referer = $_SERVER['HTTP_REFERER'] ?? ''; + +if ($origin !== '') { + if ($origin !== ALLOWED_ORIGIN) { + out(403, 'Forbidden'); + } +} elseif (strpos($referer, ALLOWED_ORIGIN . '/') !== 0) { + out(403, 'Forbidden'); +} +// ───────────────────────────────────────────────────────────────────────────── + +// ── Honeypot ────────────────────────────────────────────────────────────────── +// If the hidden "website" field is filled, it's a bot — silently pretend success. +if (!empty($_POST['website'])) { + out(200, null, true); +} + +// ── Timing check ────────────────────────────────────────────────────────────── +$loadedAt = (int) filter_input(INPUT_POST, '_t', FILTER_SANITIZE_NUMBER_INT); +$elapsed = time() - $loadedAt; + +if ($loadedAt === 0 || $elapsed < MIN_ELAPSED || $elapsed > 7200) { + out(400, 'Invalid submission'); +} + +// ── Rate limiting ───────────────────────────────────────────────────────────── +$ip = preg_replace('/[^a-f0-9:.]/', '', $_SERVER['REMOTE_ADDR'] ?? ''); +$lockDir = rtrim(RATE_DIR, '/'); + +if (!is_dir($lockDir)) { + @mkdir($lockDir, 0700, true); +} + +$lockFile = $lockDir . '/' . md5($ip) . '.lock'; + +// Atomic check+write using exclusive lock — prevents TOCTOU race condition +$fp = fopen($lockFile, 'c+'); +if ($fp === false || !flock($fp, LOCK_EX)) { + out(500, 'Server error'); +} +$lastTime = (int) fread($fp, 20); +if ($lastTime > 0 && (time() - $lastTime) < RATE_WINDOW) { + flock($fp, LOCK_UN); + fclose($fp); + out(429, 'Too many requests'); +} + +// ── Sanitize & validate ─────────────────────────────────────────────────────── +$name = clean(INPUT_POST, 'name', 100); +$email = trim(filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) ?? ''); +$subject = clean(INPUT_POST, 'subject', 200); +$message = clean(INPUT_POST, 'message', 5000); + +if ($name === '' || $email === '' || $message === '') { + flock($fp, LOCK_UN); fclose($fp); + out(400, 'Missing required fields'); +} + +if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + flock($fp, LOCK_UN); fclose($fp); + out(400, 'Invalid email address'); +} + +// ── Build and send the email via PHPMailer ──────────────────────────────────── +require __DIR__ . '/vendor/autoload.php'; + +use PHPMailer\PHPMailer\PHPMailer; +use PHPMailer\PHPMailer\SMTP; +use PHPMailer\PHPMailer\Exception as MailerException; + +$subjectLine = '[contact from danix.me] - ' . ($subject !== '' ? $subject : 'New message from ' . $name); + +$body = implode("\n", [ + 'Name: ' . $name, + 'Email: ' . $email, + 'Subject: ' . ($subject ?: '—'), + '', + str_repeat('─', 48), + '', + $message, +]); + +$mail = new PHPMailer(true); + +try { + $mail->isSMTP(); + $mail->Host = $cfg['smtp_host']; + $mail->SMTPAuth = true; + $mail->Username = $cfg['smtp_user']; + $mail->Password = $cfg['smtp_pass']; + $mail->SMTPSecure = $cfg['smtp_secure'] === 'ssl' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = $cfg['smtp_port']; + $mail->CharSet = 'UTF-8'; + + $mail->setFrom('danix@' . SENDER_DOMAIN, SENDER_DOMAIN); + $mail->addReplyTo($email, $name); + $mail->addAddress($cfg['recipient']); + + $mail->Subject = $subjectLine; + $mail->Body = $body; + + $mail->send(); + + rewind($fp); + ftruncate($fp, 0); + fwrite($fp, (string) time()); + flock($fp, LOCK_UN); + fclose($fp); + out(200, null, true); +} catch (MailerException $e) { + flock($fp, LOCK_UN); + fclose($fp); + out(500, 'Failed to send email'); +} + +// ── Helpers ─────────────────────────────────────────────────────────────────── + +function clean(int $type, string $key, int $maxLen): string +{ + $val = filter_input($type, $key, FILTER_UNSAFE_RAW) ?? ''; + $val = strip_tags(trim((string) $val)); + return mb_substr($val, 0, $maxLen); +} + +function out(int $code, ?string $error, bool $success = false): void +{ + ob_end_clean(); + http_response_code($code); + echo json_encode(['success' => $success, 'error' => $error]); + exit; +} diff --git a/static/contact.php b/static/contact.php deleted file mode 100644 index ddd3fe1..0000000 --- a/static/contact.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Contact form handler not implemented']); -} -?>