Contact & Registration

Register your script implementation or send comments and feedback.

Historical contact form from the Script Archive - now preserved as a reference.

About This Form

This page originally served two purposes: allowing users to register their implementations of Script Archive scripts (so they could be showcased) and providing a way to send feedback and comments. Users could also subscribe to the new-scripts mailing list for update notifications.

Register Scripts

Showcase your implementation to the community.

Send Comments

Share feedback, bug reports, or suggestions.

Get Notifications

Subscribe to updates for new script releases.

Historical Form Design

Below is a modern Bootstrap recreation of the original registration and comment form. This is a non-functional display showing the original form structure.

Registration and Comment Form (Historical Recreation)

General Information


If Registering a Script

Please provide the URL where the script is implemented - not your home page, but the actual script page (e.g., the form using FormMail, the page displaying the counter, etc.).

Comments


Notification of New Scripts

Subscribing to the new-scripts mailing list means you'll be notified when new versions of MSA scripts are released or updates are made. The mail load is very light - most weeks you won't receive any messages.

Note: This form is not functional. It's preserved as a historical reference showing how script registration worked in the 1990s.

Building Modern Contact Forms

Here's how you would build a similar contact form today with proper validation and security:

<!-- Modern HTML5 Contact Form -->
<form id="contactForm" action="/api/contact" method="POST" novalidate>
    <!-- CSRF Protection -->
    <input type="hidden" name="_token" value="<?= csrf_token() ?>">

    <!-- Honeypot (spam protection) -->
    <div class="d-none" aria-hidden="true">
        <input type="text" name="website" tabindex="-1" autocomplete="off">
    </div>

    <!-- Contact Type -->
    <fieldset class="mb-3">
        <legend class="h5">Contact Type</legend>
        <div class="form-check">
            <input class="form-check-input" type="radio" name="type"
                   id="typeGeneral" value="general" required checked>
            <label class="form-check-label" for="typeGeneral">
                General Inquiry
            </label>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="radio" name="type"
                   id="typeBug" value="bug">
            <label class="form-check-label" for="typeBug">
                Bug Report
            </label>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="radio" name="type"
                   id="typeFeature" value="feature">
            <label class="form-check-label" for="typeFeature">
                Feature Request
            </label>
        </div>
    </fieldset>

    <!-- Name -->
    <div class="mb-3">
        <label for="name" class="form-label">Name *</label>
        <input type="text" class="form-control" id="name" name="name"
               required minlength="2" maxlength="100"
               pattern="[A-Za-z\s\-']+">
        <div class="invalid-feedback">
            Please enter a valid name (2-100 characters).
        </div>
    </div>

    <!-- Email -->
    <div class="mb-3">
        <label for="email" class="form-label">Email *</label>
        <input type="email" class="form-control" id="email" name="email"
               required maxlength="254">
        <div class="invalid-feedback">
            Please enter a valid email address.
        </div>
    </div>

    <!-- Message -->
    <div class="mb-3">
        <label for="message" class="form-label">Message *</label>
        <textarea class="form-control" id="message" name="message"
                  rows="5" required minlength="10" maxlength="5000"></textarea>
        <div class="form-text">
            <span id="charCount">0</span> / 5000 characters
        </div>
        <div class="invalid-feedback">
            Please enter a message (10-5000 characters).
        </div>
    </div>

    <!-- reCAPTCHA -->
    <div class="mb-3">
        <div class="g-recaptcha" data-sitekey="your-site-key"></div>
    </div>

    <!-- Submit -->
    <button type="submit" class="btn btn-primary" id="submitBtn">
        <span class="spinner-border spinner-border-sm d-none" role="status"></span>
        Send Message
    </button>
</form>
<?php
// Modern PHP Contact Form Handler
// Requires: composer require phpmailer/phpmailer

namespace App\Controllers;

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

class ContactController
{
    private array $config;

    public function __construct()
    {
        $this->config = [
            'to_email' => '[email protected]',
            'to_name' => 'Support Team',
            'smtp_host' => 'smtp.example.com',
            'smtp_user' => '[email protected]',
            'smtp_pass' => getenv('SMTP_PASSWORD'),
            'recaptcha_secret' => getenv('RECAPTCHA_SECRET')
        ];
    }

    public function handle(): array
    {
        try {
            // Rate limiting
            $this->checkRateLimit();

            // CSRF validation
            $this->validateCsrf();

            // Honeypot check
            if (!empty($_POST['website'])) {
                throw new \Exception('Spam detected');
            }

            // reCAPTCHA verification
            $this->verifyRecaptcha();

            // Validate input
            $data = $this->validateInput();

            // Send email
            $this->sendEmail($data);

            // Log submission
            $this->logSubmission($data);

            return ['success' => true, 'message' => 'Message sent successfully!'];

        } catch (\Exception $e) {
            error_log('Contact form error: ' . $e->getMessage());
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    private function validateInput(): array
    {
        $rules = [
            'type' => ['required', 'in:general,bug,feature'],
            'name' => ['required', 'min:2', 'max:100', 'regex:/^[A-Za-z\s\-\']+$/'],
            'email' => ['required', 'email', 'max:254'],
            'message' => ['required', 'min:10', 'max:5000']
        ];

        $data = [];
        $errors = [];

        foreach ($rules as $field => $fieldRules) {
            $value = trim($_POST[$field] ?? '');

            foreach ($fieldRules as $rule) {
                if ($rule === 'required' && empty($value)) {
                    $errors[$field] = ucfirst($field) . ' is required';
                    break;
                }
                if (str_starts_with($rule, 'min:')) {
                    $min = (int) substr($rule, 4);
                    if (strlen($value) < $min) {
                        $errors[$field] = ucfirst($field) . " must be at least $min characters";
                        break;
                    }
                }
                if (str_starts_with($rule, 'max:')) {
                    $max = (int) substr($rule, 4);
                    if (strlen($value) > $max) {
                        $errors[$field] = ucfirst($field) . " must be at most $max characters";
                        break;
                    }
                }
                if ($rule === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $errors[$field] = 'Invalid email format';
                    break;
                }
            }

            $data[$field] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
        }

        if (!empty($errors)) {
            throw new \Exception('Validation failed: ' . implode(', ', $errors));
        }

        return $data;
    }

    private function verifyRecaptcha(): void
    {
        $recaptcha = $_POST['g-recaptcha-response'] ?? '';

        $response = file_get_contents(
            'https://www.google.com/recaptcha/api/siteverify?' . http_build_query([
                'secret' => $this->config['recaptcha_secret'],
                'response' => $recaptcha,
                'remoteip' => $_SERVER['REMOTE_ADDR']
            ])
        );

        $result = json_decode($response, true);

        if (!($result['success'] ?? false)) {
            throw new \Exception('reCAPTCHA verification failed');
        }
    }

    private function sendEmail(array $data): void
    {
        $mail = new PHPMailer(true);

        $mail->isSMTP();
        $mail->Host = $this->config['smtp_host'];
        $mail->SMTPAuth = true;
        $mail->Username = $this->config['smtp_user'];
        $mail->Password = $this->config['smtp_pass'];
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        $mail->Port = 587;

        $mail->setFrom($this->config['smtp_user'], 'Website Contact Form');
        $mail->addAddress($this->config['to_email'], $this->config['to_name']);
        $mail->addReplyTo($data['email'], $data['name']);

        $mail->isHTML(true);
        $mail->Subject = "[Contact] {$data['type']}: {$data['name']}";
        $mail->Body = $this->formatEmailBody($data);
        $mail->AltBody = strip_tags($this->formatEmailBody($data));

        $mail->send();
    }

    private function formatEmailBody(array $data): string
    {
        return "<h2>New Contact Form Submission</h2>
                <p><strong>Type:</strong> {$data['type']}</p>
                <p><strong>Name:</strong> {$data['name']}</p>
                <p><strong>Email:</strong> {$data['email']}</p>
                <p><strong>Message:</strong></p>
                <blockquote>{$data['message']}</blockquote>
                <hr>
                <p><small>Sent: " . date('Y-m-d H:i:s') . " from " .
                $_SERVER['REMOTE_ADDR'] . "</small></p>";
    }

    private function checkRateLimit(): void
    {
        $ip = $_SERVER['REMOTE_ADDR'];
        $key = "contact_rate_$ip";
        $limit = 5; // 5 submissions
        $window = 3600; // per hour

        // Implementation depends on your cache system (Redis, file, etc.)
        // This is a simplified example
        $cacheFile = sys_get_temp_dir() . '/' . md5($key);
        $count = (int) @file_get_contents($cacheFile);

        if ($count >= $limit) {
            throw new \Exception('Too many requests. Please try again later.');
        }

        file_put_contents($cacheFile, $count + 1);
    }

    private function validateCsrf(): void
    {
        // Implementation depends on your framework
        // Laravel: csrf_field() automatically validates
        // Custom: compare $_POST['_token'] with session token
    }

    private function logSubmission(array $data): void
    {
        $log = [
            'timestamp' => date('Y-m-d H:i:s'),
            'ip' => $_SERVER['REMOTE_ADDR'],
            'type' => $data['type'],
            'email' => $data['email']
        ];

        file_put_contents(
            '/var/log/contact_submissions.log',
            json_encode($log) . "\n",
            FILE_APPEND | LOCK_EX
        );
    }
}
?>
// Modern JavaScript Form Validation and Submission
class ContactForm {
    constructor(formId) {
        this.form = document.getElementById(formId);
        this.submitBtn = this.form.querySelector('[type="submit"]');
        this.spinner = this.submitBtn.querySelector('.spinner-border');

        this.init();
    }

    init() {
        // Real-time validation
        this.form.querySelectorAll('input, textarea').forEach(field => {
            field.addEventListener('blur', () => this.validateField(field));
            field.addEventListener('input', () => this.clearError(field));
        });

        // Character counter
        const message = this.form.querySelector('#message');
        const counter = this.form.querySelector('#charCount');
        message?.addEventListener('input', () => {
            counter.textContent = message.value.length;
            counter.classList.toggle('text-danger', message.value.length > 4500);
        });

        // Form submission
        this.form.addEventListener('submit', (e) => this.handleSubmit(e));
    }

    validateField(field) {
        const isValid = field.checkValidity();
        field.classList.toggle('is-valid', isValid);
        field.classList.toggle('is-invalid', !isValid);
        return isValid;
    }

    clearError(field) {
        field.classList.remove('is-invalid');
    }

    validateForm() {
        let isValid = true;

        this.form.querySelectorAll('[required]').forEach(field => {
            if (!this.validateField(field)) {
                isValid = false;
            }
        });

        return isValid;
    }

    async handleSubmit(e) {
        e.preventDefault();

        // Client-side validation
        if (!this.validateForm()) {
            this.showAlert('Please fix the errors in the form.', 'danger');
            return;
        }

        // Check reCAPTCHA
        const recaptchaResponse = grecaptcha?.getResponse();
        if (!recaptchaResponse) {
            this.showAlert('Please complete the reCAPTCHA.', 'warning');
            return;
        }

        // Show loading state
        this.setLoading(true);

        try {
            const formData = new FormData(this.form);
            formData.append('g-recaptcha-response', recaptchaResponse);

            const response = await fetch(this.form.action, {
                method: 'POST',
                body: formData,
                headers: {
                    'Accept': 'application/json'
                }
            });

            const result = await response.json();

            if (result.success) {
                this.showAlert('Message sent successfully! We\'ll get back to you soon.', 'success');
                this.form.reset();
                grecaptcha?.reset();
                this.form.querySelectorAll('.is-valid').forEach(f => f.classList.remove('is-valid'));
            } else {
                throw new Error(result.message || 'Failed to send message');
            }

        } catch (error) {
            console.error('Form submission error:', error);
            this.showAlert(error.message || 'An error occurred. Please try again.', 'danger');
        } finally {
            this.setLoading(false);
        }
    }

    setLoading(loading) {
        this.submitBtn.disabled = loading;
        this.spinner.classList.toggle('d-none', !loading);
    }

    showAlert(message, type = 'info') {
        // Remove existing alerts
        this.form.querySelectorAll('.form-alert').forEach(a => a.remove());

        const alert = document.createElement('div');
        alert.className = `alert alert-${type} form-alert mt-3`;
        alert.innerHTML = `
            
            ${message}
            
        `;

        this.form.insertBefore(alert, this.submitBtn.parentElement);

        // Auto-dismiss success messages
        if (type === 'success') {
            setTimeout(() => alert.remove(), 5000);
        }
    }
}

// Initialize form
document.addEventListener('DOMContentLoaded', () => {
    new ContactForm('contactForm');
});

// Email validation helper (RFC 5322 compliant)
function isValidEmail(email) {
    const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return pattern.test(email) && email.length <= 254;
}

// Sanitize input (prevent XSS)
function sanitizeInput(input) {
    const div = document.createElement('div');
    div.textContent = input;
    return div.innerHTML;
}

Form Security Best Practices

Common Vulnerabilities
  • Email Header Injection Attackers inject newlines to add CC/BCC recipients
  • Cross-Site Scripting (XSS) Unsanitized input displayed in admin panel
  • CSRF Attacks Forms submitted from malicious sites
  • Spam Submissions Bots flooding the form with junk data
  • Rate Limit Bypass Denial of service through mass submissions
Protection Measures
  • Input Validation Whitelist allowed characters, strip newlines from headers
  • Output Encoding Use htmlspecialchars() when displaying data
  • CSRF Tokens Unique tokens per session, validate on submission
  • CAPTCHA/Honeypots reCAPTCHA v3 or hidden honeypot fields
  • Rate Limiting Max submissions per IP per time window

Frequently Asked Questions

Script registration allowed users to showcase their implementations. The Script Archive maintained a database of sites using each script, which served as a portfolio of real-world examples. This helped other users see how scripts were being used and provided social proof of the scripts' reliability and popularity.

The implement.cgi script processed form submissions using Perl's CGI.pm module. It would parse the form data, validate required fields, and either email the submission to the site administrator or store it in a flat file. For mailing list subscriptions, it would typically interface with Majordomo or a similar list manager.

Before RSS feeds, social media, or automatic update notifications, mailing lists were the primary way to stay informed about software updates. The new-scripts list would notify subscribers of bug fixes, security patches, and new releases. This mattered because security vulnerabilities in CGI scripts were common and needed prompt attention.

Modern applications use multiple channels: GitHub Issues for bug reports and feature requests, Discourse or Discord for community discussions, email forms with ticketing systems (Zendesk, Freshdesk), social media for quick support, and documentation sites with AI-powered search. Many projects also use automated feedback widgets within their applications.

Old CGI contact forms often lacked CSRF protection, input validation, and rate limiting. The biggest issue was email header injection, where attackers could add their own headers (To, CC, BCC) by injecting newlines into form fields. This turned contact forms into spam relays. FormMail in particular was frequently exploited until security patches were released.

Use a modern framework with built-in security features (Laravel, Django, Rails), implement CSRF tokens, validate all input server-side, use CAPTCHA (reCAPTCHA v3), add rate limiting, sanitize output, and use a reputable email service (SendGrid, Mailgun, AWS SES). Never trust client-side validation alone, and always test your forms for common vulnerabilities.

Several services handle contact forms: Formspree, Netlify Forms, Google Forms, Typeform, JotForm, and Basin. These services handle spam protection, email delivery, and storage without requiring backend code. For more control, services like Formspark or FormSubmit provide API-based form handling with webhook integrations.

Use invisible reCAPTCHA v3 (no user interaction), honeypot fields (hidden inputs that bots fill), time-based validation (reject submissions faster than humanly possible), JavaScript token generation (bots often don't execute JS), and backend checks for common spam patterns. Combine multiple techniques for defense in depth without degrading user experience.

Related Resources

FormMail

The original form-to-email CGI script.

Mailing Lists

Historical discussion lists and subscription info.

Guestbook

Another form-based interactive script.