Creating a secure contact form

One common application in web development is creating a contact form.
The basic concept is very simple: you display a form, the user submits it and you send the submitted input to your self.
There is, however, and inherent danger in this form: spammer bots may use it to send spam not only to you, but also to others! And we don’t want others receiving spam from our website, do we?

So, how do they send spam to others?

An email is always composed of two basic parts: the header, which contains information like the sender, recipients, subject etc. followed by the body of the message.
So, to send spam to a whole series of other people, one could just add

\nBcc:someone@example.com

to their ’email’ input. But simply safeguarding the headers of your message won’t be enough, as, since an email is one document, the contents of the body can impact the header too.

Spam protection

One way to protect our form from spam would be to use a CAPTCHA system to make sure the user is not a machine, but then again, a human could try to send spam too.
Instead, we take two precautions: we check for any alarming strings (like bcc: or to:) and we make sure the email is a valid one (this is useful anyway).

To achieve the first goal, we’ll use a simple function and apply it to all elements in the $_POST array using the array_map function:

function despam($value) {
    # Strings to look out for:
    $bad = array('to:', 'bcc:', 'cc:', 'content-transfer-encoding:', 'multipart-mixed:', 'content-type:', 'mime-version:');
    # If we encounter any of these, return a false value
    foreach ($bad as $v) {
        if (stripos($value, $v) !== false) {
            return false;
        }
    }
    # Newline characters are required in order to send spam to others, so we remove them.
    # Unfortunately, this does mean losing some formatting, but it's ususally not an issue.
    $value = str_replace(array("\r", "\n", "%0a", "%0d"), ' ', $value);

    return trim($value);
}

# Apply our function to all items in the $_POST array
$despammed = array_map('despam', $_POST);

Finally, to make sure the user gave us a valid email, we use a Regular Expression (RegExp):

# If an email was actually provided and it didn't have any illegal characters in it:
if (isset($despammed['email']) && $despammed['email']) {
    # Define our RegExp
    $pattern = '/^[\w.-]+@[\w.-]+\.[A-Za-z]{2,6}$/';
    $subject = $despammed['email'];
    # If the email matches the RegExp
    if (preg_match($pattern, $subject)) {
        $email = $despammed['email'];
    } else {
        $errors[] = 'Please insert a valid email!';
    }

} else {
    $errors[] = 'You forgot to insert your email!';
}

And… there you have it!

With these components we can create a secure contact form. All we need to do now is bring it together in one script and display the form itself:

<?php

if (isset($_POST['contactform'])) {

    function despam($value) {
        # Strings to look out for:
        $bad = array('to:', 'bcc:', 'cc:', 'content-transfer-encoding:', 'multipart-mixed:', 'content-type:', 'mime-version:');
        # If we encounter any of these, return a false value
        foreach ($bad as $v) {
            if (stripos($value, $v) !== false) {
                return false;
            }
        }
        # Newline characters are required in order to send spam to others, so we remove them.
        # Unfortunately, this does mean losing some formatting, but it's ususally not an issue.
        $value = str_replace(array("\r", "\n", "%0a", "%0d"), ' ', $value);

        return trim($value);
    }

    $despammed = array_map('despam', $_POST);

    $errors = array(); # Errors variable


    //NAME validation
    if (isset($despammed['name']) && $despammed['name']) {

        $name = $despammed['name'];

    } elseif (isset($despammed['name']) && ($despammed['name'] == 'spam')) {

        $errors[] = 'Please inserta valid name!';

    } else {

        $errors[] = 'You forgot your name!';

    }


    //EMAIL validation
    if (isset($despammed['email']) && $despammed['email']) {
        # Define our RegExp
        $pattern = '/^[\w.-]+@[\w.-]+\.[A-Za-z]{2,6}$/';
        $subject = $despammed['email'];
        # If the email matches the RegExp
        if (preg_match($pattern, $subject)) {
            $email = $despammed['email'];
        } else {
            $errors[] = 'Please insert a valid email!';
        }

    } else {
        $errors[] = 'You forgot to insert your email!';
    }


    //MESSAGE validation
    if (isset($despammed['message']) && $despammed['message']) {

        $message = trim($despammed['message']);

    } else {

        $errors[] = 'You forgot to insert your message!';

    }

    # Send the email OR display the errors, if any
    if (empty($errors)) {

        $body = "Name: $name\n\nFrom: $email\n\nMessage: $message";

        $body = wordwrap($body, 70);

        @mail('webmaster@example.com', 'Contact Form Submission', $body, "From: $email");

        echo '<p class="success">Thank you! Your email has been sent!</p>';

    } else {

        echo '<fieldset class="error"><legend>Error!</legend>';

        foreach ($errors as $error) {

            echo " - $error
\n";

        }

        echo '</fieldset>';

    }

}

//Display the form
?>
<form action="#" method="post" name="contactform">

    <p>Name:<input type="text" name="name" size="35" maxlength="60" value="<?php if (isset($_POST['name'])) echo $_POST['name']; ?>" /></p>
    <p>Email:<input type="text" name="email" size="35" maxlength="80" value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>" /></p>
    <p>Message:<textarea name="message" rows="5" cols="30"><?php if (isset($_POST['message'])) echo $_POST['message']; ?></textarea></p>

    <input type="submit" name="submit" value="Submit!" />
</form>

Useful links

More on the stripos function: http://www.php.net/manual/en/function.stripos.php
More on the array_map function: http://www.php.net/manual/en/function.array-map.php
More on the mail function: http://www.php.net/manual/en/function.mail.php