0
\$\begingroup\$

I have a simple website contact form created back in 2017.

The form was developed using PHP, PHPMailer, jQuery, HTML and CSS.

I would like to make sure the code is up to modern standards and secure.

I would also like to switch from PHP to AJAX for success and error messaging (in order to avoid a page refresh). But that matter is probably out of scope here so I'll leave it out of the question.


Here's a detailed breakdown:

(1) The form sends messages from site users to site managers.

(2) It uses HTML for structure and CSS for presentation.

(3) User input is sent via SMTP using PHPMailer.

(4) A textarea box, with a character counter, is provided for users to submit their message.

(5) If the character count is exceeded, a jQuery script prevents the form from being submitted.

(6) Google reCAPTCHA is installed to reduce spam.

(7) HTML and standard browser functions are used for user input validation on the client-side.

(8) PHP functions are used for user input validation and sanitization on the server-side.

(9) PHP handles success and error messaging to the user.

The form works fine, as far as I can tell.


I'm looking for some guidance here:

  • The SMTP/PHPMailer set-up requires a username and password embedded in the code. Is there a more secure way?

  • Any suggestions for improving the overall quality and efficiency of this code?

Thank you.

HTML, CSS & jQuery

// text area character counter
// displays total characters allowed
// displays warning at defined count (currently 150)
// disables submit button when < 0
// max characters that can be input set by maxlength attribute in HTML
(function($) {

    $.fn.charCount = function(submit, options){

        this.submit = submit;

        // default configuration properties
        var defaults = {    
            allowed: 1250,      
            warning: 150,
            css: 'counter',
            counterElement: 'span',
            cssWarning: 'warning',
            cssExceeded: 'exceeded',
            counterText: ''
        }; 
    
        var options = $.extend(defaults, options); 

        function calculate(obj,submit){
        
            submit.attr("disabled", "disabled");

            var count = $(obj).val().length;
            var available = options.allowed - count;
            if(available <= options.warning && available >= 0){
                $(obj).next().addClass(options.cssWarning);
            } else {
                $(obj).next().removeClass(options.cssWarning);
            }
            if(available < 0){
                $(obj).next().addClass(options.cssExceeded);
            } else {
                $(obj).next().removeClass(options.cssExceeded);
                submit.removeAttr("disabled");
            }
            
            $(obj).next().html(options.counterText + available);
        };
        
        this.each(function() {              
            $(this).after('<'+ options.counterElement +' class="' + options.css + '">'+ options.counterText +'</'+ options.counterElement +'>');
            
            calculate(this, submit);

            $(this).keyup(function(){calculate(this,submit)});
            $(this).change(function(){calculate(this,submit)});
        });

    };

})(jQuery);

$(document).ready(function(){   
    $("#comments").charCount($("#submit"));
});
#contact-form {
    display: flex;
    flex-direction: column;
    width: 50%;
    font: 1rem/1.5 arial, helvetica, sans-serif;
    margin: 0 auto 1em !important;
}
#contact-form > div {
    display: flex;
    flex-direction: column;
    margin-bottom: 10px;
}
#contact-form > div:not(.send-fail-notice):not(#counter-container) {
    width: 70%;
}

#contact-form > .ad2 { align-self: flex-start; }

/* label formatting */
#contact-form > div > label {
    margin-bottom: 2px;
}

/* asterisk formatting */
#contact-form > div > label > span {
    color: #f00;
    font-size: 1.5em;
    line-height: 1;
}

/* input field formatting */
#contact-form input,
#contact-form textarea {
    border: 1px solid #ccc;
    background: #fcfcfc;
    padding: 2px 5px;
    resize: none;
    font-family: arial, helvetica, sans-serif;    
}
#contact-form input:focus,
#contact-form textarea:focus  {
    border: 1px solid #777;
}

/*  textarea and character counter */
#contact-form > #counter-container { }

#contact-form > #counter-container > .counter {
    font-size: 1.5em;
    color: #ccc;
}
#contact-form > #counter-container .warning {
    color: orange;
}
#contact-form > #counter-container .warning::after {
    content: " approaching limit";
    font-size: 1em;
}
#contact-form > #counter-container .exceeded {
    color: red;
}
#contact-form > #counter-container .exceeded::after {
    content: " form won't submit";
    font-size: 1em;
}

/*  submit button formatting */
#contact-form > button {
    align-self: flex-start;
    padding: 5px 15px;
    cursor: pointer;
    border: 1px solid #ccc;
    background-color: #f1f1f1;
    background-image: linear-gradient(#f1f1f1, #fafafa);
}

#contact-form > button:hover {
    background-image: linear-gradient(#e1e1e1, #eaeaea);
}

/* form errors */
.send-fail-notice {
    flex-direction: row !important;
    align-items: center;
    padding: 15px;    
    background-color: #ffc; 
    border: 2px solid red;    
}

.send-fail-notice > span {
    color: #e13a3e;
    font-weight: bold;
    margin-left: 10px;
}
<form method="post" id="contact-form">

  <?php echo $send_fail_one ?>
  <?php echo $send_fail_two ?>

  <div>
    <label for="name">Name <span>*</span></label>
    <input type="text" name="name" id="name" maxlength="75" value="<?php echo $name ?>" required>
  </div>

  <div>
    <label for="email">E-mail <span>*</span></label>
    <input type="email" name="email" id="email" maxlength="75" value="<?php echo $email ?>" required>
  </div>

  <div id="subject-line">
    <label for="subject">Subject</label>
    <input type="text" name="subject" id="subject" maxlength="75" value="<?php echo $subject ?>">
  </div>

  <div id="counter-container">
    <label for="comments">Message <span>*</span></label>
    <textarea name="comments" id="comments" maxlength="1500" cols="25" rows="5" required><?php echo $comments ?></textarea>
  </div>

  <div class="g-recaptcha" data-sitekey="6LdruToUAAAAAO4EZua5CV6_jREeCpv8knqklYa9" style="width: auto;"></div>

  <button type="submit" name="submit" id="submit">Send Message</button>

</form>

PHP

// CONTACT FORM PROCESSING SCRIPT

// set default values (prevents undefined variable error)
$send_fail_one = null;
$send_fail_two = null;
$name          = null;
$email         = null;
$subject       = null;
$comments      = null;

// Load PHPMailer (v 6.5.3 02/02/2022)
// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

if ($_SERVER["REQUEST_METHOD"] == "POST") {

// Load PHPMailer
require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';

// Create new PHPMailer instance; passing `true` enables exceptions
$mail = new PHPMailer(true);
$mail->CharSet = "UTF-8";

// SMTP Debugging
//SMTP::DEBUG_OFF = off (for production use)
//SMTP::DEBUG_CLIENT = client messages
//SMTP::DEBUG_SERVER = client and server messages
$mail->SMTPDebug = SMTP::DEBUG_SERVER;  
$mail->Debugoutput = 'html';

// SMTP settings
$mail->isSMTP();  
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mail->Port = 465;
$mail->Username = 'demo-purposes@yahoo.com'; 
$mail->Password = 'FakePasswordForDemoPurposes';
$mail->setFrom('demo-purposes@yahoo.com');
$mail->addAddress('demo-purposes@yahoo.com');
$mail->isHTML(true);

// Sanitize & Validate Input
// Trim all $_POST values
/* Using FILTER_SANITIZE_SPECIAL_CHARS as opposed to FILTER_SANITIZE_STRING because:
 * (1) FILTER_SANITIZE_SPECIAL_CHARS will disarm code but still display it.
 * (2) FILTER_SANITIZE_STRING removes all code, leaving no trace, and all code input is lost (e.g., anything with brackets is lost).
 * Hence, with (1) we can identify users trying to submit code.
 * Exception: textarea field ('comments') uses FILTER_SANITIZE_STRING because otherwise nl2br (keep line breaks) doesn't work. */
$name = trim(filter_input(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS));
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$subject = trim(filter_input(INPUT_POST, 'subject', FILTER_SANITIZE_SPECIAL_CHARS));
$comments = nl2br(filter_input(INPUT_POST, 'comments', FILTER_SANITIZE_STRING));

// Email Body
$message = <<<HTML
<br>
<table style="border: 1px solid #e1e1e1; background-color: #fafafa; margin: 0 auto; width: 75%; padding: 10px 20px;">
<tr><td style="padding: 10px 0;"><b>NAME</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$name</td></tr>
<tr><td style="padding: 10px 0;"><b>E-MAIL</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$email</td></tr>
<tr><td style="padding: 10px 0;"><b>SUBJECT</b></td></tr>
<tr><td style="border-bottom: 1px solid #e1e1e1; padding-bottom: 15px;">$subject</td></tr>
<tr><td style="padding: 10px 0;"><b>MESSAGE</b></td></tr>
<tr><td style="padding-bottom: 15px;">$comments<td></tr>
</table>
HTML;

$mail->Subject = 'Message Received =?utf-8?B?4oCT?= Website Contact Form';
$mail->Body = $message;

// verify recaptcha response
$url        =  "https://www.google.com/recaptcha/api/siteverify";
$privatekey =  "xxx";
$response   =  file_get_contents($url."?secret=".$privatekey."&response=".$_POST['g-recaptcha-response']."&remoteip=".$_SERVER['REMOTE_ADDR']);
$data       =  json_decode($response);

// if recaptcha verification is a success...
if(isset($data->success) AND $data->success==true) {
            
    // ... and if phpmailer does not send (for whatever reason, such as a wrong SMTP password)...
    if (!$mail->send()) { 

        // then show this error message:    
        $send_fail_one = <<<ERROR
            <div class="send-fail-notice">
                <img src="images/warning-sign.gif" height="44" width="50" alt="error sign">
                <span>ERROR. Message not sent.<br>Please try again or contact us at <i><u>bireg</u></i> <i><u>at</u></i> <i><u>outlook</u></i> <i><u>dot</u></i> <i><u>com</u></i>.</span>
            </div>  
ERROR;
}   else {
        // ...otherwise delivery is successful and page re-directs to thank you / confirmation page
        header('Location: https://www.yahoo.com');
    }
            
   } else {
       
        // if re-captcha verification fails, show this error message:
        $send_fail_two = <<<ERROR
            <div class="send-fail-notice">
                <img src="images/warning-sign.gif" height="44" width="50" alt="error sign">
                <span>ERROR. Message not sent.<br>Please check the anti-spam box.</span>
            </div>  
ERROR;
        }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Welcome to CodeReview. The standard on this site is that the title should reflect what the code does, since "is my code good enough" would apply to too many questions. See How to Ask. \$\endgroup\$
    – Teepeemm
    Feb 6, 2022 at 2:23
  • \$\begingroup\$ @Teepeemm, thanks for the suggestion. I edited the title. \$\endgroup\$ Feb 6, 2022 at 2:33

1 Answer 1

1
\$\begingroup\$

Contact forms don't have to be a security risk, but it all depends on what you do with the submitted data. There are basically two types of security you need to keep in mind:

1. The server

You don't store anything in a database, or sent the e-mails yourself. This takes away one of the biggest threats to your server. You've left out a lot of code, so it is still possible you do store the data, but clearly you don't want this to be discussed here.

2. The visitor

Users run a much bigger risk with this contact form. Apart from their comment, they have to supply their name and email address. Why exactly?

Any smart user would, of course, not use their own name and e-mail address. Any email address will do. So, why do ask for it?

You could make supplying a name and email address optional.

You are also using Google's hidden reCAPTCHA, which hands over a lot of user data to Google.

Read: Google reCAPTCHA and the GDPR: a possible conflict?

If the reCAPTCHA misses any of that information Google has a second chance by peeking in the mails you so kindly send via their mail server. You don't leave your visitors any choice, if they want to comment they could as well be sending their comments straight to Google.

Why would this be a problem? Google stored this data in the USA. And the US government likes big data. They use it. It's easy. Edward Snowden showed us how. Foreigners have even less protection under US law.

Also Read: Exclusive: Government Secretly Orders Google To Identify Anyone Who Searched A Sexual Assault Victim’s Name, Address Or Telephone Number.

Google is one of the major reasons there's no privacy on the internet, and they actively work, along with Facebook, to keep it that way. They pursue legitimate business interests, but the result is quite damaging.

Most people, including me, too often ignore this obvious problem. We concentrate on all the technical issues, but there's a whole other side to this story.

\$\endgroup\$
2
  • \$\begingroup\$ Not exactly what I was looking for, but useful and relevant information nonetheless. Thank you. \$\endgroup\$ Feb 11, 2022 at 19:13
  • \$\begingroup\$ "Apart from their comment, they have to supply their name and email address. Why exactly?" Because it's a contact form, and we need to know where and to whom to respond. \$\endgroup\$ Feb 11, 2022 at 19:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.