NOTE: "name" attribute is deprecated, except for forms (and PHP $_POST array depends upon it) so you may want to use both "name" and "id".
Click a line of code to jump to examples.
Forms submit their values via ACTION. When SUBMIT is clicked, control is passed to the ACTION reference.
action="apage.html"
How the values arrive is determined by METHOD
method="post" or method="get"
Use GET with GET, POST with POST, REQUEST with either
$_GET['formfield']<form id="myform" name="myform" action="apage.html" method="get"> $_POST['formfield']<form id="myform" name="myform" action="apage.html" method="post"> $_REQUEST['formfield'](REQUEST processes variables transmitted by GET, POST, or COOKIE)
See below for examples of processing forms.
<label for="lblname">Enter your last name:</label> <input id="lname" type="text" size="30" />
The "FOR" attribute associates the label with the field. For radio buttons and checkboxes, clicking the label yields the same result as clicking the input.
You can align your form fields by styling LABEL with CSS.
/* CSS rule */ label { width: 10em; float: left; }
IE6, the polyp of web browsers, will often screw up floats. You may need to declare the containing DIV as position "relative" if it is inside a complex structure.
The "ACCESSKEY" attribute is supposed to work in LABEL, too, but I'm not getting any joy from it.
<input type="text" id="xxx" name="xxx" size="12" /> <input type="text" id="xxx" name="xxx" value="some value" size="12" maxlength="15" />
Can include javascript events such as "onchange", "onmouseover", "onmouseout", etc.
The scripted version below loads field with PHP variable and upper cases the text after user has altered it:
<script language="JavaScript" type="text/javascript"> function ucase(myfield) { var d = document.getElementById(myfield); if (d.value.length > 0) { d.value = d.value.toUpperCase(); } } </script> <input type="text" size="12" maxlength="15" id="xxx" name="xxx" value="<?php echo $xxxval; ?>" onchange="ucase('xxx')" />
<input type="password" id="xxx" name="xxx" size="12" maxlength="25" />
Like text, but browser displays asterisks instead of text.
<input type="hidden" id="xxx" name="xxx" />
Hidden elements are not secure. Anyone can look at the page's source and see and alter values stored there.
They can be convenient holders for values used by scripts or other pages, just don't trust them more than other form elements.
<input id="chk_1" name="chk_1" type="checkbox" checked="checked" />
The script below creates checkboxes based upon the value of bits in an integer.
<script language="JavaScript" type="text/javascript"> function testbit($b,$f) { // is bit ON, True/False return (($f >> $b) & 1==1); // does (($flags shifted RIGHT by bit) AND 1) = 1 ? } function setbit($b,$f) { // turn bit ON return ((1 << $b) | $f); // 1 shifted LEFT by bit, locates current bit, set with OR } function clearbit($b,$f) { // turn bit OFF return ((0xFFFF - (1 << $b)) & $f); // locate bit, subtract it from $FFFF, clear with AND } </script> <?php # code assumes $flags is integer whose bits hold the value of each checkbox (16 boxes possible) # and $flag_title array[0..n] is loaded with titles retrieved from mysql_query for ($i=0; $i<$chk_max; $i++) { $checked = (testbit($i,$flags)) ? "checked='checked'" : ''; echo "<p><input name='chk_" . $i . "' type='checkbox' $checked /> {$flag_title[$i]}</p>\n"; } ?>
<input type="radio" name="sex" value="male" checked="checked"> male <br /> <input type="radio" name="sex" value="female"> female <br /> <input type="radio" name="sex" value="other"> other <br />
A click on any button clears others of the same name.
<input type="button" value="button name" />
Buttons need to be associated with an "event" to be useful.
Here, ONCLICK passes a reference to the entire form, so doSomething() can access other form fields:
<input type="button" value="button name" onclick="doSomething(this.form)" />
The doSomething() script could access other form elements like this:
function doSomething(myform) { var ln = myform.lastname; var fn = myform.firstname; var fullname = fn + ' ' + ln; }
<input type="reset" />
This example renames the button and confirms the action:
<form onReset="return confirm('This will undo your changes. Proceed?')"> <input type="reset" value="Reset Form"> </form>
An ONCLICK event could analyze other fields or vars to decide whether or not to allow or confirm:
<form . . . > <input type="reset" value="Reset Form" onclick="resetform()"> </form>
In general, RESET may be more confusing than useful. Users might confuse it with SUBMIT & lose their work.
<textarea name="notes" id="notes" rows="7" cols="65"></textarea> <textarea name="notes" id="notes" rows="7" cols="65"><?php echo $mytext; ?></textarea>
<form id="myform" name="myform"> <select id="mylist" name="mylist"> <option value="" selected="selected"> </option> <option value='1' >option 1</option> <option value='2' >option 2</option> <option value='3' >option 3</option> </select> </form>
To reference a selected option:
document.myform.mylist.options[document.myform.mylist.selectedIndex].value
To populate select options from query results:
// $oldselect stores a previous selection (if any) <select id="mylist" name="mylist"> <?php $sql = "select id, title from t_lists where groupID = $mygroup order by grpsort"; $r = mysql_query($sql); while ($listrow = mysql_fetch_assoc($r)) { $selected = ($listrow['id'] == $oldselect) ? "selected='selected'" : ''; echo "<option value='{$listrow['id']}' $selected>{$listrow['title']}</option>\n"; } ?> </select>
To act on selection change — declare "onchange" event in SELECT itself:
<select id="mylist" name="mylist" onchange="window.location='mypage.php?var=' + document.myform.mylist.options[document.myform.mylist.selectedIndex].value">
<select id="mylist" name="mylist" multiple="multiple" size="3"> <option value='1'> option 1 </option> <option value='2'> option 2 </option> <option value='3'> option 3 </option> </select>
Multiple selects require use of CTRL or SHIFT keys.
Javascript to test for multiple selections in a list:
// call this function with "searchList('listname')" function searchList(e); mylist = document.getElementById(e); for(var i=0; i < mylist.length; i++) { if (mylist.options[i].selected == true) { //do something useful } } }
<input type="submit" id="OK" name="OK" value="OK" default="yes" />
In this example, javascript checks for required fields prior to submitting form.
<script language="JavaScript" type="text/javascript"> function chkfield(f) { // verify form field has some length var e = document.getElementById(f); if (e.value.length <= 0) { e.focus(); return false; } else { return true; } } function checkFields() { var OK = 'Y'; /* check in reverse order so focus winds up on first unfilled field */ if (!chkfield("field2")) { OK = 'N'; } if (!chkfield("field1")) { OK = 'N'; } if (OK == 'N') { alert("Please provide information for each category."); return false; } else { return true; } } </script> <input type="submit" id="OK" name="OK" value="OK" default="yes" onclick="checkFields()" /> <input type="submit" id="cancel" name="cancel" value="cancel" onclick="window.close()") />
You can approximate the "submit" type through a standard HREF tag combined with javascript.
In the next example, the HREF is pointed to a non-existent ID (#),
and the browser's normal attempt to locate it is aborted by "return false" — which is appended to whatever scripts are invoked.
<a href="#" onclick="someScript(); return false;"> OK </a>
Below is a FORM that POSTs its values to the same page — PHP decides whether to process or display.
<?php if (isset($_POST['xxx'])) { // . . . process variables // . . . redirect to page in parent folder$clean('host') = ( check_input( $_SERVER['HTTP_HOST'],'host' ) ) ? $_SERVER['HTTP_HOST'] : false; if ($clean['host']) { $html['host'] = htmlentities($clean['host']); // not sure about this header("Location: http://".$html['host'].dirname($_SERVER['PHP_SELF'])."/../apage.html"); exit; } else { die; } } else { ?>
"check_input" (below) is a function you would create to test the host nameregular HTML goes here, including . . .
<form id="myform" name="myform" action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post"> <p>Required Field: <input type="text" id="xxx" name="xxx" size="12" /></p> </form> <?php } ?>
ACTION can add arguments, even if the method is POST.
(Process arguments with GET, forms values with POST, or both with REQUEST.)
action="<?php echo $_SERVER['PHP_SELF'] . "?arg1=$arg1&arg2-arg2"; ?>" method="post"
If you display error messages as you process form values, then redirect with javascript.
<script language="JavaScript1.2"> location.href = "../apage.html"; /* go to a page in parent folder */ </script>
Although you might create a single file to display and process a web form, that doesn't mean incoming data were created by your form.
A wiley user can analyze your form, and send your script any malicious code he chooses. The phone number you think you're getting might be a specially-crafted bit of script designed to deface your page, break your application, or steal information.
Assume all incoming data are tainted. As the graphic shows, this includes PHP arrays and the results from HTTP requests or MySQL queries. All could contain unexpected, corrupt, or malicious data.
The following is based on suggestions from Chris Shiflett's PHP Security Briefing (which, along with the rest of his brainbulb site, seems to have vanished).
A consistent naming convention helps show what you can trust, and what's ready for output. Shifflet suggests 3 arrays for keeping track of variables:
$clean = array(); // stores all filtered data (none of it escaped) $mysql = array(); // stores escaped data ready for insertion into database $html = array(); // stores escaped data for display or transmittal to another web resource
The idea is simple. No input to your application is trusted until it has been tested and placed inside $clean. $clean is the single source of data for futher processing.
And $clean is the only source of data for $mysql and $html. (They never draw data from each other because their data are escaped for different contexts.) They become the sole sources of data for SQL queries or HTML output.
Use a "whitelist" approach When possible, allow a set of known values, rather than try to anticipate all bad values.
switch ($val) { case 'apples' : case 'oranges' : case 'bananas' : return true; break; default : return false; }
Test input length If too big, generate an error message or lop it off (as shown here).
$clean['val'] = (strlen($_POST['val'])>$max) ? substr($_POST['val'],0,$max-1) : $_POST['val'];
Does the variable contain only:
These can be combined:
$clean['val'] = ctype_alnum($_POST['val']) or ctype_space($_POST['val']); // allow letters, numbers, or spaces
Test character type (PHP provides several true/false character type functions.)
$clean['val'] = ctype_alnum($_POST['val']); // does $_POST['val'] contain only alphanumeric characters?
Weed out potential trouble PHP can strip tags and script functions.
$clean['val'] = strip_tags($_POST['val'],'<b><i><u>'); // remove all HTML tags, except b, i, & u
For simple string replacements, use STR_REPLACE — much faster than EREG_REPLACE or PREG_REPLACE. The first example illustrates how the contents of one array can replace the substrings listed in another array. (htmlentities() would be a better tool for this particular case.)
$example = "<p>This is a paragraph.</p>"; echo str_replace( array('<','>'), array('<','>'), $example);
You can replace several strings with a single string, using str_replace or regular expressions. Here are 2 examples of replacing common javascript function names with "forbidden." (The 2nd example comes from "guestbook." It has the advantage of ignoring case.)
$stripAttrib = array( "javascript:","onload","onclick","ondblclick","onmousedown","onmouseup","onmouseover" "onmousemove","onmouseout","onkeypress","onkeydown","onkeyup","style","class","id" ); str_replace( $stripAttrib, 'forbidden', $value); $stripAttrib = 'javascript:|onload|onclick|ondblclick|onmousedown|onmouseup|onmouseover| onmousemove|onmouseout|onkeypress|onkeydown|onkeyup|style|class|id'; preg_replace("/$stripAttrib/i", 'forbidden', $value);
h.o matches hoo, h2o, h/o, etc.
cat|dog match cat or dog
[ \ ^ $ . | ? * + ( ) { }
ring\? matches ring?
\(quiet\) matches (quiet)
c:\\windows matches c:\windows
[\D\S] means not digit OR whitespace, both match
[^\d\s] disallow digit AND whitespace
colou?r match color or colour
[BW]ill[ieamy's]* match Bill, Willy, William's etc.
[a-zA-Z]+ match 1 or more letters
\d{3}-\d{2}-\d{4} match a SSN
[a-zA-Z]{2,} 2 or more letters
[a-z]\w{1,7} match a UW NetID
(see modifiers)
\bring word starts with "ring", ex ringtone
ring\b word ends with "ring", ex spring
\b9\b match single digit 9, not 19, 91, 99, etc..
\b[a-zA-Z]{6}\b match 6-letter words
\Bring\B match springs and wringer
^\d*$ entire string must be digits
^[a-zA-Z]{4,20}$ string must have 4-20 letters
^[A-Z] string must begin with capital letter
[\.!?"')]$ string must end with terminal puncutation
\$_(GET|POST|REQUEST|COOKIE|SESSION|SERVER)\[.+\]
Negative lookbehind needs fixed-width pattern (here, s\b). To find words NOT starting with "s", this fails — \b(?<!s)\w+\b — because "\w+" isn't a fixed width.
match text bound by simple matching HTML tags — \w+ does not match tags with attributes (?<=<(\w+)>).*(?=</\1>)
Lookaround groups help locate positions. The pattern to match above is .* (any characters 0 to many times). The groups before and after it determine when it should start and stop capturing.
If the first lookbehind group never matches, capturing never begins. Note, this matches only the text, not the tags themselves.
match complex opening & closing xhtml tags and all text between (?i)<([a-z][a-z0-9]*)[^>]*>.*?</\1>
Here, the first capture group is limited to just the tag name. Its potential attributes are outside the group.
(NB: This markup — <a onclick="valueOK('a>b')"> — would end the match early. It doesn't matter in this case — the subsequent back reference will pull the match to the closing tag. But if you used a group to capture the opening tag, it might be truncated in rare cases.)
IF condition — phone number w/optional parentheses around area code (and optional space after closing parens) (\()?\d{3}(?(1)\) ?|[- \.])\d{3}[- \.]\d{4}
The IF condition can be set by a backreference (as here) or by a lookaround group.
groups can be named (assume a file of lastname, firstname altered using "preg_replace()") (?#find)(\b.+), (\b.*\b) (?#replace)\2 \1 (?#find)(?P<lname>\b.+), (?P<fname>\b.*\b) (?#replace) (?P=fname) (?P=lname)
For a good overview: http://www.codeproject.com/KB/dotnet/regextutorial.aspx.
For a good tutorial: http://www.regular-expressions.info.
Test input pattern Regular expressions can check for complex expected patterns. Here's a cheat sheet.
The next code allows 4-28 letters, numbers, or underscores (and ignores case).
preg_match returns 1 (if pattern is found in value) 0 (if not). (PHP treats non-zero as TRUE.)
preg_match( pattern , value ); // place pattern inside "/.../", and inside "/^ ... $/" to match entire value
preg_match( "/Jack/" , $v ); // true if $v contains "Jack"
preg_match( "/^Jack$/" , $v ); // true if $v equals "Jack"
preg_match( "/^[a-zA-Z'-\s]+$/" , $v ); // does $v contain only letters, apostrophes, hyphens or spaces?
$clean['val'] = (preg_match('/^[\w]{4,28}$/i', $_POST['val']) ) ? $_POST['val'] : false; // set false if match fails
All patterns your form might generate could be tested in a single function.
The next code tests 'lastname' (sent via POST) against an expected "name" pattern. If the pattern matches, $clean['lastname'] is set to $_POST['lastname'], otherwise to FALSE.
$clean = array(); $clean['lastname']=(checkinput($_POST['lastname'],'name')) ? $_POST['lastname'] : false; function checkinput( $s, $type ) { switch ($type) { case "name" : // allow any number of a-z, hyphen, space, period, apostrophe (ignore case) return preg_match( '/^[a-z-\'\. ]*$/i' , $s ); break; case "int" : return preg_match('/^[0-9]{1,8}$/' , $s); // 1-8 digits between 0 and 9 break; case "email" : // allow dots in account.name, only 2-4 characters in domain (ignore case) return preg_match('/^[a-z]{1}[\w]+([.][\w]+)*[@][\w]+([.][\w]+)*[.][a-z]{2,4}$/i', $s ); break; case "USphone" : // US-style phone format return preg_match('/(\()?\d{3}(?(1)\) ?|[-/ \.])\d{3}[- \.]\d{4}$/', $s); break; } // end switch }
A consistent naming convention helps here. If the $clean array never holds escaped data, you know its data should be escaped before leaving your application.
$mysql['val'] = mysql_real_escape_string($clean['val']); $result = mysql_query( " insert into users set colname = '{$mysql['val']}' " );
$html['val'] = htmlentities($clean['val']); echo "<p>{$html['val']}</p>";
PHP's "foreach" construction provides a rapid way to escape all cleaned data:
foreach ($clean as $k => $v) { $mysql[$k] = mysql_real_escape_string($v); } foreach ($clean as $k => $v) { $html[$k] = htmlentities($v); }
Both functions preserve characters that might have special meaning in another context. If PHP lacks a routine for your database server, fall back to addslashes().
mysql_escape_real_string protects special characters and can guard against some forms of attack. (See the next link for extended examples.)
Accepting input at face value can lead to situations like this. Assume a user enters his lastname as " O'Rourke ", and your script processes it this way:
$lastname = $_GET['lastname']; mysql_query("insert into users set lastname='$lastname'");The resulting SQL statement becomes: " insert into users set lastname='O'Rourke' ".
Mysql_real_escape_string() "escapes" potentially troublesome characters by adding backslashes to them.
$mysql['lastname'] = mysql_real_escape_string($clean['lastname']); mysql_query("insert into users set lastname={$mysql['lastname']}");The SQL now is: " insert into users set lastname='O\'Rourke' ".
More importantly, mysql_real_escape_string() helps ward off some types of SQL injection attacks.
Say your database host allows stacked queries. (MySQL doesn't, but MSSQL does.) You could lose your entire users table if a malicious user entered this as his last name: " '; drop table users -- ".mssql_query("insert into users set lastname='$lastname'");The SQL now resolves to: " insert into users set lastname=' '; drop table users -- ' ".
Mysql_real_escape_string() neutralizes this attack by escaping the initial single quote.
These lines should provide a dual line of defense:$mysql['lastname'] = mysql_real_escape_string($clean['lastname']); mssql_query("insert into users set lastname={$mysql['lastname']}");Your filtering shouldn't allow semi-colons in a lastname, so $clean['lastname'] should be null or false.
is_name($v) { // does $v contain only letters, apostrophes, hyphens, or white space? return preg_match( "/^[a-zA-Z'-\s]+$/" , $v ) // return true or false } $clean['lastname'] = (is_name($_POST['lastname'])) ? $_POST['lastname'] : false;If your filtering fails, mysql_real_escape_string would cause your SQL to become:
But mysql_real_escape_string() can't guard against all types of injection.
Say your form feeds your script $_POST['selectedname'], when a user chooses a name in a <SELECT> field.$result = $mysql_query( "select * from users where lastname='{$_POST['selectedname']}' ");Unfortunately, you can't depend on the <SELECT> field to limit your users' choices.
$clean['lastname'] = ( is_name($_POST['lastname']) ) ? $_POST['lastname'] : false; if ($clean['$lastname']) { $mysql['lastname'] = mysql_real_escape_string($clean['lastname']); $result = $mysql_query(" select * from users where lastname= '{$mysql['lastname']}' "); } else { // handle error here, perhaps: $html['error'] = htmlentities("Sorry. I don't recognize your selection."); echo "<p>{$html['error']}</p>"; }
htmlentities protects text in URLs, and is a handy way to display source code.
$clean['msg'] = "<p>Here's the markup.</p>"; $html['msg'] = htmlentities($clean['msg']); echo "<p>{$html['msg']}</p>";
If you want to display data about to be committed to the database, or provide a summary after it has been, your code should look something like this:
$mysql['val'] = mysql_real_escape_string($clean['val']); $html['val'] = htmlentities($clean['val']); echo "<p>You are about to insert {$html['val']} into the database.</p>";