- Remembering the Basics
- Validating Form Data
- Using PECL Filter
- Authentication with PEAR Auth
- Using MCrypt
Validating Form Data
Handling form data is still far and away the most common use of PHP (in this author's humble opinion, anyway). The security concern lies in the fact that the PHP page handling the form will do something with the information the user enters: store it in a database, pass it along to another page, or use it in an email. If the information the user enters is tainted, you could have a major problem on your hands. As a rule, do not trust the user! Mistakes can happen, either on purpose or by accident, that could reveal flaws in your code, cause the loss of data, or bring your entire system to a crashing halt.
Some good validation techniques are:
- Use the checkdate() function to confirm that a given date is valid.
- Typecast numbers.
- Use regular expressions to check email addresses, URLs, and other items with definable patterns (see the sidebar).
As with the basic security techniques already reviewed, the hope is that as a somewhat-experienced PHP programmer, you already know most of these things. To be certain, this next example will present a sample registration form (Figure 4.1), taking various types of information, which will then be precisely validated. In doing so, I'll make use of a couple of Character Type functions, added to PHP in version 4.3. Listed in Table 4.1, these functions test a given value against certain constraints for the current locale (established by the setlocale() function).
Figure 4.1 When users first come to the registration page, this is the form they will see.
Table 4.1. The Character Type functions provide validation specific to the given environment (i.e., the locale setting).
Character Type Functions |
|
Function |
Checks If Value Contains |
ctype_alnum() |
Letters and numbers |
ctype_alpha() |
Letters only |
ctype_cntrl() |
Control characters |
ctype_digit() |
Numbers |
ctype_graph() |
Printable characters, except spaces |
ctype_lower() |
Lowercase letters |
ctype_print() |
Printable characters |
ctype_punct() |
Punctuation |
ctype_space() |
White space characters |
ctype_upper() |
Uppercase characters |
ctype_xdigit() |
Hexadecimal numbers |
To validate a form
- Begin a new PHP script in your text editor or IDE, starting with the HTML (Script 4.1).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" /> <title>Registration Form</title> </head> <body> <?php # Script 4.1 - register.php
Script 4.1. This page both displays a registration form and processes it. The script validates the submitted data using various functions, and then reports any errors.
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 4 <head> 5 <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" /> 6 <title>Registration Form</title> 7 </head> 8 <body> 9 <?php # Script 4.1 - register.php 10 11 /* This page creates a registration form 12 * which is then validated using various functions. 13 */ 14 15 if (isset($_POST['submitted'])) { // Handle the form. 16 17 // Store errors in an array: 18 $errors = array(); 19 20 // Check for non-empty name: 21 if (!isset($_POST['name']) OR empty($_POST['name'])) { 22 $errors[] = 'name'; 23 } 24 25 // Validate the email address using eregi(): 26 if (!eregi('^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', $_POST['email'])) { 27 $errors[] = 'email address'; 28 } 29 30 // Validate the password using ctype_alnum(): 31 if (!ctype_alnum($_POST['pass'])) { 32 $errors[] = 'password'; 33 } 34 35 // Validate the date of birth using check_date(): 36 if (isset($_POST['dob']) AND 37 (strlen($_POST['dob']) >= 8) AND 38 (strlen($_POST['dob']) <= 10) ) { 39 40 // Break up the string: 41 $dob = explode('/', $_POST['dob']); 42 43 // Were three parts returned? 44 if (count($dob) == 3) { 45 46 // Is it a valid date? 47 if (!checkdate((int) $dob[0], (int) $dob[1], (int) $dob[2])) { 48 $errors[] = 'date of birth'; 49 } 50 51 } else { // Invalid format. 52 $errors[] = 'date of birth'; 53 } 54 55 } else { // Empty or not the right length. 56 $errors[] = 'date of birth'; 57 } 58 59 // Validate the ICQ number using ctype_digit(): 60 if (!ctype_digit($_POST['icq'])) { 61 $errors[] = 'ICQ number'; 62 } 63 64 // Check for non-empty comments: 65 if (!isset($_POST['comments']) OR empty($_POST['comments'])) { 66 $errors[] = 'comments'; 67 } 68 69 if (empty($errors)) { // Success! 70 71 // Print a message and quit the script: 72 echo '<p>You have successfully registered (but not really).</p></body></html>'; 73 exit(); 74 75 } else { // Report the errors. 76 77 echo '<p>Problems exist with the following field(s):<ul>'; 78 79 foreach ($errors as $error) { 80 echo "<li>$error</li>\n"; 81 } 82 83 echo '</ul></p>'; 84 85 } 86 87 } // End of $_POST['submitted'] IF. 88 89 // Show the form. 90 ?> 91 <form method="post"> 92 <fieldset> 93 <legend>Registration Form</legend> 94 <p>Name: <input type="text" name="name" /></p> 95 <p>Email Address: <input type="text" name="email" /></p> 96 <p>Password: <input type="password" name="pass" /> (Letters and numbers only.)</p> 97 <p>Date of Birth: <input type="text" name="dob" value="MM/DD/YYYY" /></p> 98 <p>ICQ Number: <input type="text" name="icq" /></p> 99 <p>Comments: <textarea name="comments" rows="5" cols="40"></textarea></p> 100 101 <input type="hidden" name="submitted" value="true" /> 102 <input type="submit" name="submit" value="Submit" /> 103 </fieldset> 104 </form> 105 106 </body> 107 </html>
Create the section of the script that handles the submitted form.
if (isset($_POST['submitted'])) { $errors = array();
Your script should always handle the form before it could possibly redisplay it (on errors found). I like to use a hidden form input to check if a form was submitted. The hidden form input will always be passed to the page upon submission, unlike any other input (on Internet Explorer, if a user submits a button by pressing Enter, then the submit button won't be set).
One way I like to validate forms is to use an array that stores the errors as they occur. By checking if this array is empty, the script can tell if all validation tests have been passed. If the array isn't empty, its values can be used to print the error messages.
- Check for a name.
if (!isset($_POST['name']) OR empty($_POST['name'])) { $errors[] = 'name'; }
A person's name is one of those things that you can use regular expressions on, but it may not be worthwhile. A valid name can contain letters, spaces, periods, hyphens, and apostrophes. Under most circumstances, just checking for a nonempty name is sufficient. - Validate the submitted email address.
if (!eregi('^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', $_POST['email'])) { $errors[] = 'email address'; }
There are any number of patterns you can use to validate an email address, depending on how strict or liberal you want to be. This one is commonly seen. Certainly some invalid email addresses could slip through this expression, but it does add a sufficient level of security. Feel free to use a different pattern if you have one to your liking. Keep in mind that a user could enter a valid e-mail address that does not actually exist. Only some sort of activation process (sending the user an email containing a link back to the Web site) can confirm a real address. Validate the submitted password.
if (!ctype_alnum($_POST['pass'])) { $errors[] = 'password'; }
The form indicates that the password must contain only letters and numbers. To validate such values, the function ctype_alnum() works perfectly.
In a real registration form, I would also recommend confirming the password with a second password input, then making sure both values match. I'm skipping that step here for brevity's sake.
Begin checking to see if the user entered a valid date of birth.
if (isset($_POST['dob']) AND (strlen($_POST['dob']) >= 8) AND (strlen($_POST['dob']) <= 10) ) { $dob = explode('/', $_POST['dob']); if (count($dob) == 3) {
There is really no way of knowing if the information users enter is in fact their birthday, but PHP's built-in checkdate() function can confirm whether or not that date existed. Since the form takes the date of birth as a simple string in the format MM/DD/YYYY, the script must first confirm that something was entered. I also check if the string's length is at least eight characters long (e.g., 1/1/1900) but no more than ten characters long (e.g., 12/31/2000).
This string is then exploded on the slashes to theoretically retrieve the month, day, and year values. Next, a conditional checks that exactly three parts were created by the explosion.
- Check if the date of birth is a valid date.
if (!checkdate((int) $dob[0], (int) $dob[1], (int) $dob[2])) { $errors[] = 'date of birth'; }
The checkdate() function confirms that a date is valid. You might want to also check that a user didn't enter a date of birth that's in the future or the too-recent past. Each value is typecast as an integer as an extra precaution. - Complete the date of birth conditionals.
} else { $errors[] = 'date of birth'; } } else { $errors[] = 'date of birth'; }
The first else applies if the submitted value cannot be exploded into three parts. The second else applies if the value isn't of the right length. - Validate the ICQ number.
if (!ctype_digit($_POST['icq'])) { $errors[] = 'ICQ number'; }
The ICQ number can only contain digits, so it makes sense to use the ctype_digit() function. - Check for some comments.
if (!isset($_POST['comments']) OR empty($_POST['comments'])) { $errors[] = 'comments'; }
Comments really cannot be run through a regular expression pattern because any valid pattern would allow just about anything. Instead, a check for some value is made. - If there were no errors, report upon the success.
if (empty($errors)) { echo '<p>You have successfully registered (but not really).</p></body></html>'; exit();
If no errors occurred, then $errors would still be empty. The script could then register the user (probably in a database). Here it just prints a message and terminates the script (so that the form isn't redisplayed) instead (Figure 4.2).Figure 4.2 If all of the data passed through the various checks, this message is displayed.
- Report the errors.
} else { echo '<p>Problems exist with the following field(s):<ul>'; foreach ($errors as $error) { echo "<li>$error</li>\n"; } echo '</ul></p>'; }
If $errors isn't empty, it contains all of the fields that failed a validation test. These can be printed in a list (Figure 4.3).Figure 4.3 A lot of mistakes were made in this registration attempt, each reported back to the user.
- Complete the main conditional and the PHP code.
} // End of $_POST['submitted'] IF. ?>
- Create the HTML form.
<form method="post"> <fieldset> <legend>Registration Form</legend> <p>Name: <input type="text" name="name" /></p> <p>Email Address: <input type="text" name="email" /></p> <p>Password: <input type="password" name="pass" /> (Letters and numbers only.)</p> <p>Date of Birth: <input type="text" name="dob" value="MM/DD/YYYY" /></p> <p>ICQ Number: <input type="text" name="icq" /></p> <p>Comments: <textarea name="comments" rows="5" cols="40"></textarea></p> <input type="hidden" name="submitted" value="true" /> <input type="submit" name="submit" value="Submit" /> </fieldset> </form>
There's not much to say about the form except to point out that it does indicate the proper format for the password and date of birth fields. If you are validating data to a specification, it's important that the end user be made aware of the requirements as well, prior to submitting the form. - Complete the page.
</body> </html>
- Save the file as register.php, place it in your Web directory, and test in your Web browser.