Jump to content
Larry Ullman's Book Forums
Sign in to follow this  
Matt

Sending Http Response Codes With Ajax

Recommended Posts

Larry,

 

I know this probably belongs in the Javascript forum as it involves AJAX, but because it focuses on HTTP Status Codes, I just decided to put it here instead!

 

I was doing a quick search for "Contact Form" scripts because I wanted to see what the new trends were for submitting them and handling errors and I came across a good tutorial from Treehouse. I noticed that they were setting HTTP response code headers before sending the response back to the client.

 

Here is the AJAX script:

<?php

    // Only process POST reqeusts.
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        // Get the form fields and remove whitespace.
        $name = strip_tags(trim($_POST["name"]));
				$name = str_replace(array("\r","\n"),array(" "," "),$name);
        $email = filter_var(trim($_POST["email"]), FILTER_SANITIZE_EMAIL);
        $message = trim($_POST["message"]);

        // Check that data was sent to the mailer.
        if ( empty($name) OR empty($message) OR !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            // Set a 400 (bad request) response code and exit.
            http_response_code(400);
            echo "Oops! There was a problem with your submission. Please complete the form and try again.";
            exit;
        }

        // Set the recipient email address.
        // FIXME: Update this to your desired email address.
        $recipient = "hello@example.com";

        // Set the email subject.
        $subject = "New contact from $name";

        // Build the email content.
        $email_content = "Name: $name\n";
        $email_content .= "Email: $email\n\n";
        $email_content .= "Message:\n$message\n";

        // Build the email headers.
        $email_headers = "From: $name <$email>";

        // Send the email.
        if (mail($recipient, $subject, $email_content, $email_headers)) {
            // Set a 200 (okay) response code.
            http_response_code(200);
            echo "Thank You! Your message has been sent.";
        } else {
            // Set a 500 (internal server error) response code.
            http_response_code(500);
            echo "Oops! Something went wrong and we couldn't send your message.";
        }

    } else {
        // Not a POST request, set a 403 (forbidden) response code.
        http_response_code(403);
        echo "There was a problem with your submission, please try again.";
    }

?>

What is the purpose of sending a response code? The script is already sending back an error message if there are any problems with the form submission, so why would you want to do this?

 

I also had another question about error checking. Is it better to do this on the PHP side (i.e. with AJAX) or with javascript?

 

Thanks for any help or advice you can give!

 

Matt

Share this post


Link to post
Share on other sites

That code is providing two pieces of information: an HTTP status code and a message (through the echo statement). This allows the PHP script (which is the endpoint for the Ajax request) to indicate both a good/bad status and a specific message. In the corresponding JavaScript, you can then look at the status code and respond accordingly (i.e., do this for a 4xx code; do that for a 2xx). If you don't use a status code, presumably all request statuses will be 200s, unless the JavaScript couldn't make the request of the PHP script. 

 

Put another way, without status codes, what the Ajax request receives back is a status indicator for whether the request to the PHP script went through. With status codes, the Ajax request receives back a status indicator for the whole attempt: accessing the page, and using it properly. 

 

Error checking needs to be done in JavaScript and PHP both. JavaScript is for convenience; PHP for security. 

Share this post


Link to post
Share on other sites

Larry,

 

Thank you for the clear and well explained answer. By sending back the response codes, I am assuming that I can then display very specific error messages that are also styled for that error.

 

Error checking needs to be done in JavaScript and PHP both. JavaScript is for convenience; PHP for security. 

 

Sorry Larry, I wrote that last question quickly and didn't word it correctly. I was absolutely going to do error checking with php and just forgot when I was writing the post that the ajax script has to do error checking along with the unobtrusive version of the page. What I wanted to know is if it's better to just do all the error checking in the ajax script and send back the corresponding errors (like the script is doing), or do some error checking in javascript first before even sending off the request? If it's done in javascript, then it will reduce the number of server requests.

Share this post


Link to post
Share on other sites

Yeah, yeah. I'd do basic error checking in the JavaScript before attempting to make the Ajax request. No need to make another server request unless there's a good reason to!

Share this post


Link to post
Share on other sites

Larry,

 

Thanks for the help and sorry for the late response!

 

I started working on the contact form and using the http_response_codes. They seem to work fine, but when when the script fails, it displays an error in the console window showing the error and the ajax script which failed. Is this what is supposed to happen? The average user won't see it, but it is still showing an error which bugs me.

 

Also, I am trying to follow all the modern best practices for implementing the form, as contact forms are a very popular attack vector. It seems like the sanitization people use is all over the place. Some remove new lines from the name field, others use strip_tags. In the PHP6 And MySql5 book, you had a contact form in Chapter 12 which used a spam_scrubber() function. It used a "blacklist" to clean values that might be used to send spam by altering the email's header. In the new book, you took this out. Was there a reason for this?

 

On top of this, I can't use a regex on the name field as I am allowing all Western characters, as well as Japanese, to be entered. What is the best way to stop spam attempts?

 

Thanks again,

 

Matt

Share this post


Link to post
Share on other sites

Hey Matt! As for the error, you're not really providing enough information for me to suggest any causes/solutions. 

 

For the spam_scrubber(), I don't think I took that out. It's in Script 13.1. 

 

Preventing spam is really a matter of applying lots of techniques. One good, non-CAPTCHA, route is to have a non-displaying text input (hidden via CSS). A bot will populate that field with a value, especially if it has a meaningful name, whereas a human wouldn't. 

Share this post


Link to post
Share on other sites

Larry,

 

Sorry for not giving more information about the error. Basically, when I was testing the ajax script, there were some bugs that were causing it to fail certain conditionals. When that happened it was sending the appropriate error message back as json data as well as the http_response_code. In the javascript console it would display an error like the following:

 

Error with contact_form.php 403 (forbidden)

 

Is it normal for this to be displayed in the console upon sending an http_response_code for an error?

 

As far as the span_scrubber, I was looking at the code for email.php in chapter 11 (I don't have the newest edition of the book, so I was just looking at the downloadable code). You are right that version 13.1 does in fact include the spam scrubber, so my apologies for that.

 

I notice that you are running all three input fields through it. I know that the only inputs to watch out for are the ones that are added to the mail header (i.e. 'email' or possibly 'name'), so is it necessary to run the 'comment' through it as well? Also, wouldn't running the email variable through FILTER_VALIDATE_EMAIL take care of the carriage returns or another email address being added to it?

 

Thanks,

 

Matt

Share this post


Link to post
Share on other sites

Yes, if the Ajax script returns an error type status code, that would be reported in the console (this is a downside of the approach, as the request of the page worked, but the proper request of the page is being reported). 

 

As for the spam scrubber, any data that gets added to the email can be used to manipulate the headers, not just "email" and "name". An email is essentially one text file, so everything the user enters goes into that one file.

 

Yes, FILTER_VALIDATE_EMAIL would balk at the carriage returns or another email address being added. 

Share this post


Link to post
Share on other sites

Larry,

 

Thanks for the advice! I implemented your spam_scrubber() function and everything works great!

 

Also, the http_status_codes are useful in that I can listen for a failure in my ajax callback and react accordingly. I suppose I could also have done this by sending back a boolean and using a conditional in the success handler to decide what to do, but it does allow very precise control over the how to handle different types of errors.

 

Also, have you heard of ReCAPTCHA? HartleySan told me about it the other day and it looks pretty amazing! I would still validate everything, but it does stop the bots from getting in.

 

Thanks,

 

Matt

Share this post


Link to post
Share on other sites

Larry,

 

Sorry to keep bombarding you with posts, but I just wanted to post one more time to show you my code for the contact form. I have taken your code and added a few other checks to make it more secure. You know I am very strict about security (as you are), and I want this form to stop any kind of attack, while at the same time being easy to use. Please take a quick look at it and if you have any suggestions or advice, please let me know.

<?php
    require('config.inc.php');
    require(SITE_FUNCTIONS);
    
    $model = array(
        'errors' => array()
    );
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (isset($_POST['data'])) {

            $data = gpd(); // This function runs json_decode and converts the object to an array

            $inputs = $data['inputs'];

            function spam_scrubber($value) {
                
                // List of very bad values:
                $very_bad = array('to:', 'cc:', 'bcc:', 'content-type:', 'mime-version:', 'multipart-mixed:', 'content-transfer-encoding:');
            
                // If any of the very bad strings are in he submitted value, return an empty string:
                foreach ($very_bad as $v) {
                    if (stripos($value, $v) !== false) {
                        return '';
                    }
                }
            
                // Replace any newline characters with spaces:
                $value = str_replace(array( "\r", "\n", "%0a", "%0d"), ' ', $value);
            
                // Return the value:
                return trim($value);
        
            } // End of spam_scrubber() function.
            
            // Clean the form data:
            $scrubbed = array_map('spam_scrubber', $inputs);
            
            if (isset($scrubbed['name']) && isset($scrubbed['email']) && isset($scrubbed['message'])) {

                $scrubbed['name'] = strip_tags($scrubbed['name']);
                
                if (!empty($scrubbed['name'])) {
                    /* The name input accepts characters from any language and this makes sure it is 40 characters or less. */
                    if (mb_strlen($scrubbed['name'], 'utf-8') < 41) {
                        $name = $scrubbed['name'];
                    } else {
                        $model['errors']['name'] = array(
                            'id' => 'name',
                            'message' => 'The Name field must not exceed 40 characters in length.'
                        );
                    }
                } else {
                    $model['errors']['name'] = array(
                        'id' => 'name',
                        'message' => 'The Name field is required.'
                    );
                }
                
                if (!empty($scrubbed['email'])) {
                    if (filter_var($scrubbed['email'], FILTER_VALIDATE_EMAIL)) {
                        $email = $scrubbed['email'];
                    } else {
                        $model['errors']['email'] = array(
                            'id' => 'email',
                            'message' => 'The Email field must contain a valid email address.'
                        );
                    }
                } else {
                    $model['errors']['email'] = array(
                        'id' => 'email',
                        'message' => 'The email field is required.'
                    );
                }
                
                if (!empty($scrubbed['message'])) {
                    /* The 'message' field accepts characters from any language and this makes sure that it is 250 characters or less. */
                    if (mb_strlen($scrubbed['message'], 'utf-8') < 251) {
                        $message = $scrubbed['message'];
                    } else {
                        $model['errors']['message'] = array(
                            'id' => 'message',
                            'message' => 'The Message ' . mb_strlen($scrubbed['message'], 'utf-8') . ' field must not exceed 250 characters in length.'
                        );
                    }
                } else {
                    $model['errors']['message'] = array(
                        'id' => 'message',
                        'message' => 'Please enter a message for the Message field.'
                    );
                }
                
                if (!empty($model['errors'])) {

                    http_response_code(400);

                    $failure = array(
                        'message' => 'There was a problem with your submission. Please complete the form and try again.',
                        'errors' => $model['errors']
                    );
                    
                    echo json_encode($failure);
                    
                    exit;
                }
                
                /* Code to insert into database and send mail */
                
                if (/* Mail and insert were successful */) {
                    
                    http_response_code(200);
                    
                    $success = array(
                        'message' => 'Thank you! Your message has been sent.'
                    );
                    
                    echo json_encode($success);
                } else {
                    
                    html_response_code(500);
                    
                    $failure = array(
                        'message' => 'Something went wrong and we couldn\'t send your message.'
                    );
                    
                    echo json_encode($failure);
                }
                
            } else {
                
                http_response_code(403);
                
                $failure = array(
                    'message' => 'There was a problem with your submission. Please try again.'
                );
                
                echo json_encode($failure);
            }
        } else {
            
            http_response_code(403);
            
            $failure = array(
                'message' => 'There was a problem with your submission. Please try again.'
            );
            
            echo json_encode($failure);
        }
    } else {
        
        http_response_code(403);
        
        $failure = array(
            'message' => 'There was a problem with your submission. Please try again.'
        );
        
        echo json_encode($failure);
    }

Thanks again Larry for all your help! I really appreciate it!

 

Matt

Share this post


Link to post
Share on other sites

The main thing I think is that your maximum length of the name and message limits are arbitrary and problematic. Not a big deal, but it's not something I would do. 

Share this post


Link to post
Share on other sites

Larry,

 

Thanks for looking at the code! I ended up getting rid of the http_response_code stuff as it wasn't really necessary for the form to function correctly. Also, what should I be checking for with the name and message fields (I can't use a regex because the form allows both English and Japanese to be entered)?

 

Thanks,

 

Matt

Share this post


Link to post
Share on other sites

I'd just check that it doesn't include values that would be used for sending spam (i.e., run it through the spam scrubber). 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...