Serverside Webscripting [JLW322]

03.forms

Before we begin ...

Combining HTML and PHP

  • All PHP code must be enclosed by <?php and ?>
  • Text residing outside <?php and ?> does not result in an error but just is not parsed
    • → It is perfectly possible to combine HTML and PHP
      <!DOCTYPE html>
      <html>
      <head>
          <title>Combining HTML and PHP</title>
          <meta charset="UTF-8" />
      </head>
      <body>
      	<p>I am HTML</p>
      	<?php echo '<p>I am HTML, generated by PHP</p>'; ?>
      </body>
      </html>

Whitespace in the browser (1)

  • Echo'ing a PHP_EOL to the browser does not result in a newline on the screen (but it does in the source code)
    <?php
    	for ($i = 0; $i < 5; $i++) {
    		echo $i+1 . PHP_EOL;
    	}
  • You'll also need to output a <br />
    <?php
    	for ($i = 0; $i < 5; $i++) {
    		echo $i+1 . '<br />' . PHP_EOL;
    	}

Whitespace in the browser (2)

  • What about var_dump() or print_r()?
    <?php
    	var_dump($_SERVER);
  • No single-line way to convert the newlines to <br />
    • → Luckily HTML provides us with the <pre> element
      <?php
      	function dump($var) {
      		echo '<pre>';
      		var_dump($var);
      		echo '</pre>';
      	}
      
      	dump($_SERVER);
Note: $_SERVER is a superglobal holding information about the server and execution environment

Headers (1)

  • It's possible to manipulate the headers sent via the response head from within PHP
  • Just use the header() function
    <?php
    
    	// Instruct the browser to redirect to ikdoeict.be
    	header('Location: http://www.ikdoeict.be');
    	// @note: example is not complete (yet)

Headers (2)

  • When redirecting (e.g. in a scenario not being logged in for example) you MUST stop the script executing further to prevent the content from being transmitted to the user
  • Scripts can be stopped prematurely via exit()
    <?php
    
    	// Instruct the browser to redirect to ikdoeict.be
    	header('Location: http://www.ikdoeict.be');
    	exit();
    
    	echo 'This line will be ignored';

Headers (3)

  • As per protocol spec, the response head must be sent before response body
  • Put differently: once you've started sending the response body, it's impossible to manipulate the response head
    <?php
    
    	echo 'Will it blend?';
    
    	header('Location: http://www.ikdoeict.be');
    	exit();
    Warning: Cannot modify header information - headers already sent by (output started at assets/03/examples/redirect_fail.php:3) in assets/03/examples/redirect_fail.php on line 5

Form Basics

Example form

Our testform

Let's take a look at assets/03/examples/form_01.php

A proper HTML Form (1)

  • If your form is built properly, it's easy to work with forms in PHP
  • Important HTML <form> attributes
    • method — HTTP method to use; get or post (default: get)
    • action — Where to send the form-data to when submitted
    • enctype — How the form-data should be encoded when using post

A proper HTML Form (2)

  • Always give <input> and <textarea> elements a name
    • Radiobuttons: same name in same set
    • Checkboxes: same name with square brackets in same set
  • Always give <select> elements a name
    • Specify a value for each <option> of the <select>
  • id attributes not mandatory for proper functioning
    • Webbrowsers & servers use the name attributes
    • Only to be added for JS purposes
    • Remember an id must be unique

Querystring

Querystring?

  • Upon submitting your form, the browser will collect all (*) form-data in one text string named the querystring
    • (*) Not all really, more on that later
  • Querystring format: key=value&key=value&key=value
    • The keys are the name attributes of the elements, the values the entered/selected value
  • The browser then makes a request to the URL defined in the form action attribute and pass that querystring to it

GET vs POST (1)

  • With GET all values of the querystring are attached to the form its target URL
    • The entered values are visible in the URL of the browser
    • In order to prevent conflicts the values are URL encoded before being attached
      • Think & and = that could break the format of the querystring
  • With POST the querystring is sent in the request body
    • The entered values are not visible in the URL of the browser, only via sniffing

GET vs POST (2)

Charles output of a form sent via GET
Charles output of a form sent via GET
Charles output of a form sent via POST
Charles output of a form sent via POST

URL Encoding

  • A few frequently used characters
    Character URL Encoded
    $ %24
    & %26
    < %3C
    > %3E
    ? %3F
    ; %3B
    # %23
    / %3A
    = %3D
    , %2C
    " %22
    ' %27
    + %2B
    % %25

After sending

  • After sending, the receiving PHP script will decode the querystring and fill some associate arrays with the sent-in values
    • $_GET — All values sent via GET
    • $_POST — All values sent via POST
    • $_REQUEST — All values sent via GET and POST
      • Deprecated, don't ever use this!

$_GET / $_POST Example

Our testform

Let's take a look at assets/03/examples/form_01.php and assets/03/examples/form_process.php

Querystring revisited

  • Upon submitting, all form fields are included in the querystring, except:
    • Controls without a name attribute (demo)
    • Unchecked radiobuttons (demo)
    • Disabled controls (demo)
    • Buttons that were not clicked (demo)

Summarized example: our testform

Our testform

Sidenote: combining GET and POST

  • Possible if you already have parameters added onto the target URL
    <form action="form_process.php?foo=getfoo&amp;bar=getbar" method="post">
    
    	<fieldset>
    		<h2>Get and Post</h2>
    		<dl class="clearfix">
    			<dt><label for="foo">Foo?</label></dt>
    			<dd class="text">
    				<input type="text" id="foo" name="foo" value="" />
    			</dd>
    
    			<dt><label for="baz">Baz?</label></dt>
    			<dd class="text">
    				<input type="text" id="baz" name="baz" value="" />
    			</dd>
    
    			<dt class="full clearfix" id="lastrow">
    				<input type="submit" name="btnSubmit" value="Send" />
    			</dt>
    		</dl>
    	</fieldset>
    </form>

When to use GET or POST (1)

  • Use GET for read actions
    • showArticle()
    • showComments()
    • getSearchResults()
  • Use POST for manipulative and sensitive actions
    • addComment()
    • deleteUser()
    • login()
  • Use POST when sending files
  • Use POST for lengthy data (Max length URLs: 2048 chars)

When to use GET or POST (2)

  • Examples of how it's not done:
    • Consider these links
      • index.php?module=blog&action=delete&id=1
      • login.php?username=bramus&password=Azerty123
      • like.php?page=HotBabes123
    • Why not GET?
      • GET can be bookmarked
      • GET can be distributed/embedded (iframe)
      • GET can be cached by webspiders
      • GET remains visible in the browser history
      • GET link can become invalid over time
    • → Use POST instead!

PHP Form Processing

Sending forms

  • The action attribute defines where to send a form to
  • In PHP we'll opt to send a form itself
  • Instead of typing the name of the file in manually, you can let PHP automatically output the URL of the page itself into the action attribute's value
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    	...
    </form>
→ This way we can put a file anywhere on the server, it will always submit to itself

Default code structure

  • A PHP page typically consists of two parts:
    1. PHP Logic at the top
    2. HTML (with a little PHP in between) at the bottom
  • In the first part we'll:
    • Get all values from $_GET or $_POST
    • Process the form (if it was submitted)
  • In the second part we'll:
    • Put our HTML
    • Use PHP to persist the values entered (if it was submitted)
The line separating the two can be recognized by ?><!DOCTYPE html>

Persisting form values

Persistency

  • = After having submitted the form to itself, make it show the values you entered again.
  • Different types of inputs (text, checkbox, dropdown) require different persistency methods

Persisting text inputs

  • <input type="text|password"> and <textarea>
    • Just output the sent-in value
    <?php
    
    	// Get variable values from $_GET
    	$name = $_GET['name'];
    	$pass = $_GET['pass'];
    	$remark = $_GET['remark'];
    
    ?><DOCTYPE html>
    
    ...
    
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    
    	<input type="text" id="name" name="name"
    		value="<?php echo $name; ?>" />
    
    	<input type="password" id="pass" name="pass"
    			value="<?php echo $pass; ?>" />
    
    	<textarea name="remark" id="remark" rows="5"
    		cols="40"><?php echo $remark; ?></textarea>
    
    </form>

Persisting dropdowns

  • <select>
    • For each <option>, check if the value matches the sent-in value
    • If so: set the selected="selected" attribute
    <?php
    
    	// Get variable values from $_GET
    	$cont = (int) $_GET['cont'];
    
    ?><DOCTYPE html>
    
    ...
    
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    	<select name="cont" id="cont">
    		<option value="0"<?php if ($cont == 0) { echo ' selected="selected"'; } ?>>Please select...</option>
    		<option value="1"<?php if ($cont == 1) { echo ' selected="selected"'; } ?>>Africa</option>
    		<option value="2"<?php if ($cont == 2) { echo ' selected="selected"'; } ?>>America</option>
    		<option value="3"<?php if ($cont == 3) { echo ' selected="selected"'; } ?>>Antarctica</option>
    		<option value="4"<?php if ($cont == 4) { echo ' selected="selected"'; } ?>>Asia</option>
    		<option value="5"<?php if ($cont == 5) { echo ' selected="selected"'; } ?>>Europe</option>
    		<option value="6"<?php if ($cont == 6) { echo ' selected="selected"'; } ?>>Oceania</option>
    	</select>
    </form>

Persisting radiobtns & checkboxes

  • <input type="radio|checkbox">
    • For each radiobutton, check if the value matches the sent-in value
    • If so: set the checked="checked" attribute
    <?php
    
    	// Get variable values from $_GET
    	$gender = $_GET['gender'];
    	$meals = $_GET['meals'];
    
    ?><DOCTYPE html>
    
    ...
    
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    
    	<input type="radio" name="gender" value="male"<?php if ($gender
    		 == 'male') { echo ' checked="checked"'; } ?> />
    	<input type="radio" name="gender" value="female"<?php if ($gender
    		 == 'female') { echo ' checked="checked"'; } ?> />
    
    	<input type="checkbox" name="meals[]" value="breakfast"<?php if (in_array(
    		'breakfast', $meals)) { echo ' checked="checked"'; } ?> />
    	<input type="checkbox" name="meals[]" value="lunch"<?php if (in_array(
    		'lunch', $meals)) { echo ' checked="checked"'; } ?> />
    	<input type="checkbox" name="meals[]" value="dinner"<?php if (in_array(
    		'dinner', $meals)) { echo ' checked="checked"'; } ?> />
    
    </form>

That's it?

  • Not really, as we've been cheating in the code examples by suppressing errors
  • In reality there are a few pitfalls
    • Undefined Index
    • htmlentities()
    • Magic Quotes (pre PHP 5.4)

Pitfall: Undefined Index (1)

  • When not suppressing errors, with exactly the same PHP code, we get a rather nasty result
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    	(same HTML code as before)
    </form>
  • Problem: before submitting $_GET and $_POST are empty
    • The code stumbles upon the fact that we want to use a variable such as $_GET['name']; when it in fact is non-existent
    • If we change all error texts with normal data it works fine

Pitfall: Undefined Index (2)

  • Fix: check with isset() if a variable exists before using it
    <?php
    
    	$name = isset($_GET['name']) ? $_GET['name'] : '';
    	...
    ?><DOCTYPE html>
  • Caution: make sure the default value has the correct type
    <?php
    
    	$meals = isset($_GET['meals']) ? (array) $_GET['meals'] : array();
    	...
    ?><DOCTYPE html>

Pitfall: htmlentities/XSS (1)

  • If we enter some HTML-looking text, we break the form
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    	(same HTML code as before)
    </form>
    • When entering hello " /> test we get this HTML
      <input type="text" id="name" name="name" value="hello" /> test" />
  • Fix: convert special characters with htmlentities() to their HTML safe / encoded version when outputting on screen
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
    	<input type="text" id="name" name="name"
    	value="<?php echo htmlentities($name); ?>" />
    </form>

Pitfall: htmlentities/XSS (2)

!! If you don't prevent XSS in the assignments or your exam, you will fail them !!

Formchecking

Formchecking

  • = Check all values of a sent form for validity
  • If all checks out
    1. Perform an action (e.g. send an e-mail, insert a record into a database, etc.)
    2. Redirect to a thanks/OK page using header('Location: ...');
  • If it doesn't check out
    1. Build a list of error messages
    2. Continue with the script an show the form, including its error messages

Why formchecking?

  • Don't ever trust user input
    • Data might be crafted in a way to harm your server (XSS!)
    • Data might be in the wrong format (e.g. dates)
  • Even if a user means no harm, he might just have forgotten to complete a field
  • Therefore you must verify all incoming data
    • If any data is missing or malformed, inform the user with a proper message

How to know if a form was sent? (1)

  • Remember: Clicked buttons are also sent
    <?php
    
    	// form was sent!
    	if (isset($_POST['btnSubmit'])) {
    
    		// @TODO: put formchecking logic here
    
    	}
    
    	...

How to know if a form was sent? (2)

  • Pro-tip: Use a hidden field instead of trusting the button being clicked
    <?php
    
    	$moduleAction = isset($_POST['moduleAction']) ? $_POST['moduleAction'] : '';
    
    	// Form was sent!
    	if ($moduleAction == 'processName') {
    		// @TODO: put formchecking logic here
    	}
    
    ?><!DOCTYPE html>
    
    	...
    
    	<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    
    		...
    
    		<input type="hidden" name="moduleAction" value="processName" />
    		<input type="submit" id="btnSubmit" name="btnSubmit" value="Send" />
    
    		...
    
    	</form>

How to know if a form was sent? (3)

  • Bonus: it affords to have one page process multiple forms
    <?php
    
    	// initial values
    	$moduleAction = isset($_POST['moduleAction']) ? $_POST['moduleAction'] : '';
    
    	// form #1 was sent
    	if ($moduleAction == 'processName') {
    		// @TODO: put formchecking (form #1 only) logic here
    	}
    
    	// form #2 was sent
    	if ($moduleAction == 'processAge')) {
    		// @TODO: put formchecking (form #2 only) logic here
    	}
    
    	// ...
    
    ?><!DOCTYPE html>
    <html>
    ...
    <body>
    
    	<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    
    		...
    
    		<input type="hidden" name="moduleAction" value="processName" />
    		<input type="submit" id="btnSubmit" name="btnSubmit" value="Send" />
    
    		...
    
    	</form>
    
    	<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    
    		...
    
    		<input type="hidden" name="moduleAction" value="processAge" />
    		<input type="submit" id="btnSubmit" name="btnSubmit" value="Send" />
    
    		...
    
    	</form>
    </body>
    </html>

Formchecking Example (Simplified)

<?php

	// initial values
	$moduleAction = isset($_POST['moduleAction']) ? $_POST['moduleAction'] : '';
	$name = isset($_POST['name']) ? (string) $_POST['name'] : '';
	$msgName = '*';

	// form is sent
	if ($moduleAction == 'processName') {

		$allOk = true;

		// name not set, or empty
		if (trim($name) == '') {
			$msgName = 'Please enter your name';
			$allOk = false;
		}

		// end of form check.
		// If $allOk still is true, then the form was sent in correctly
		if ($allOk === true) {
			header('Location: formchecking_thanks.php?name=' .urlencode($name));
			exit();
		}

	}
	...

Sidenote: urlencode

  • When redirecting with a parameter in the URL, you must always urlencode() it manually
    • Again, never trust user submitted data
    • The browser does this for you when submitting a form, but as we're doing it on the server now …
  • Not encoded
    $gun = 'smith&wesson';
    header('Location: redirect_process.php?gun=' . $gun);
    exit(0); // do not forget to exit after redirect
  • Encoded
    $gun = 'smith&wesson';
    header('Location: redirect_process.php?gun=' . urlencode($gun));
    exit(0); // do not forget to exit after redirect

File Uploads

File Uploads

  • Preparation
    • Add the enctype="multipart/form-data" attribute to the form
    • Set the form method to post
  • Handling file uploads
    • PHP will fill the $_FILES superglobal with info about each file sent
    • File uploads are stored in a temp folder on the server, you'll have to move them manually
      see next workshop

Demo

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">

	<fieldset>

		<h2>Testform</h2>

		<dl class="clearfix">

			<dt><label for="image">Image</label></dt>
			<dd><input type="file" id="image" name="image" value="" /></dd>

			<dt>
				<input type="hidden" name="moduleAction" value="processUpload" />
				<input type="submit" name="btnSubmit" value="Send" />
			</dt>

		</dl>

	</fieldset>

</form>
Note: it is not possible to persist files!

Under the hood

  • The browser will send the file as an entity
    • Entities are sent in the request body
    • Entities are separated by a boundary
      • The boundary is randomly generated by the browser
    • Each entity has it's own headers identifying the entity
Charles output of a file sent via POST
Charles output of a file sent via POST

Questions?

Code summary

A code-only summary of this chapter is available at 03.forms.summary.html

Note: not all information from these slides can be found in this summary!

Sources

  • ...
ikdoeict.be