21

Avoid resending a form

Create site13 by copying site12.

  1. /cms
    1. ...
    2. site12
    3. site13

In this chapter, we are going to program reading a form in order to avoid its execution after a simple resend by a navigator or a robot.

To test the result online, enter http://www.frasq.org/cms/site13 in the address bar of your navigator. Fill in the form and press Send. The form returns with a confirmation message. The subject and the text of the message are erased. Come back to the previous page in the history of the navigator. The page displays the form with the subject and the text of the message that you have already sent. Click on Send. The form returns unchanged and with no confirmation message. It hasn't been executed for a second time.

In order to make sure that the dialogue is continuous, not repeated, the program must check if the form which is received is the last one which was sent. A unique marker, also called a token, which is saved in the session, is added to the form in a hidden field, then, when the forms returns, the marker extracted from the form is compared with the one in memory. If they don't match, the form isn't validated.

Add the file tokenid.php in the folder library with the following content:

  1. function token_id() {
  2.     return md5(uniqid(rand(), TRUE));
  3. }

token_id returns the MD5 digest on 32 bytes of the unique identifier returned by the PHP function uniqid.

$ php -a
php > echo md5(uniqid(rand(), TRUE));
3018357bade6cfcab3d0b1abd51add45

Modify the file blocks/mailme.php which defines the mailme function which generated the block of the form:

  1. require_once 'tokenid.php';

Loads the code of the token_id function.

  1.     $_SESSION['mailme_token'] = $token = token_id();

Registers in the session the unique marker generated by the token_id function.

  1.     $mail=$subject=$message=$code=$token=false;

Initializes the variables read in the form.

  1.             if (isset($_POST['mailme_token'])) {
  2.                 $token=readarg($_POST['mailme_token']);
  3.             }

Reads the value of the token in the form.

  1.     $bad_token=false;

Initializes the error variable which contains the test result of the token.

  1.     switch($action) {
  2.         case 'send':
  3.             if (!isset($_SESSION['mailme_token']) or $token != $_SESSION['mailme_token']) {
  4.                 $bad_token=true;
  5.                 break;
  6.             }

Checks if the token in memory and the token in the form match.

  1.     switch($action) {
  2.         case 'send':
  3.             if ($bad_token or $missing_code or $bad_code or $missing_mail or $bad_mail or $missing_subject or $bad_subject or $missing_message) {
  4.                 break;
  5.             }

Doesn't execute the form if the token hasn't been validated.

  1.     $with_captcha=false;
  1.             if ($with_captcha) {
  2.                 if (!$code) {
  3.                     $missing_code=true;
  4.                     break;
  5.                 }
  6.                 $captcha=isset($_SESSION['captcha']) ? $_SESSION['captcha'] : false;
  7.                 if (!$captcha or $captcha != strtoupper($code)) {
  8.                     $bad_code=true;
  9.                     break;
  10.                 }
  11.             }

Makes the captcha optional. NOTE: Using a captcha makes a token redundant.

  1.     $output = view('mailme', $lang, compact('token', 'with_captcha', 'mail', 'subject', 'message', 'errors', 'infos'));

Generates the view with the value of the token and the captcha option.

Notice that mailme filters the field for the email address with strflat:

  1.     switch($action) {
  2.         case 'send':
  3.             if (isset($_POST['mailme_mail'])) {
  4.                 $mail=strtolower(strflat(strip_tags(readarg($_POST['mailme_mail'], true))));
  5.             }

Add the file strflat.php in the folder library with the following content:

  1. /cms/site13
    1. library
      1. strflat.php
  1. function strflat($s) {
  2.     $from = array(
  3.                 'à', 'â', 'ä', 'á', 'ã', 'å',
  4.                 'î', 'ï', 'ì', 'í',
  5.                 'ô', 'ö', 'ò', 'ó', 'õ', 'ø',
  6.                 'ù', 'û', 'ü', 'ú',
  7.                 'é', 'è', 'ê', 'ë',
  8.                 'ç', 'ñ',
  9.                 'À', 'Â', 'Ä', 'Á', 'Ã', 'Å',
  10.                 'Î', 'Ï', 'Ì', 'Í',
  11.                 'Ô', 'Ö', 'Ò', 'Ó', 'Õ', 'Ø',
  12.                 'Ù', 'Û', 'Ü', 'Ú',
  13.                 'É', 'È', 'Ê', 'Ë',
  14.                 'Ç', 'Ñ',
  15.     );
  16.  
  17.     $to = array(
  18.                 'a', 'a', 'a', 'a', 'a', 'a',
  19.                 'i', 'i', 'i', 'i',
  20.                 'o', 'o', 'o', 'o', 'o', 'o',
  21.                 'u', 'u', 'u', 'u',
  22.                 'e', 'e', 'e', 'e',
  23.                 'c', 'n',
  24.                 'A', 'A', 'A', 'A', 'A', 'A',
  25.                 'I', 'I', 'I', 'I',
  26.                 'O', 'O', 'O', 'O', 'O', 'O',
  27.                 'U', 'U', 'U', 'U',
  28.                 'E', 'E', 'E', 'E',
  29.                 'C', 'N',
  30.     );
  31.  
  32.     return str_replace($from, $to, $s);
  33. }

strflat returns $s without accents.

Modify the view of the form, in English and in French, to add the token and manage the optional display of the captcha:

  1. /cms/site13
    1. views
      1. en
        1. mailme.phtml
      2. fr
        1. mailme.phtml
  1. <form action="" method="post">
  2. <input type="hidden" name="mailme_token" value="<?php echo $token; ?>" />

Adds the hidden field mailme_token to the form.

  1. <?php if ($with_captcha): ?>
  2. <img class="captcha" src="<?php echo $base_path; ?>/captcha" alt="" title="Verification code" />

Doesn't display the captcha if $with_captcha isn't true.

Apply the same modifications to the French version:

  1. <?php if ($with_captcha): ?>
  2. <img class="captcha" src="<?php echo $base_path; ?>/captcha" alt="" title="Code de vérification" />

To send the form or change the focus from one field to another when the user presses the Enter key, we need some code in Javascript.

Add a folder called js directly under the root of the site:

  1. /cms/site13
    1. js

Add the file tools.js in the folder js with the following content:

  1. /cms/site13
    1. js
      1. tools.js
  1. function focusonenter(e, id) {
  2.     var keycode;
  3.    
  4.     if (window.event)
  5.         keycode = window.event.keyCode;
  6.     else if (e)
  7.         keycode = e.which;
  8.     else
  9.         return true;
  10.  
  11.     if (keycode == 13) {
  12.         var field = document.getElementById(id);
  13.         if (field)
  14.             field.focus();
  15.         return false;
  16.     }
  17.     else
  18.         return true;
  19. }

The function focusonenter moves the focus of the navigator to the tag id if a keyboard event with code 13, the Enter key, has been detected.

  1. function submitonenter(e, id) {
  2.     var keycode;
  3.    
  4.     if (window.event)
  5.         keycode = window.event.keyCode;
  6.     else if (e)
  7.         keycode = e.which;
  8.     else
  9.         return true;
  10.  
  11.     if (keycode == 13) {
  12.         var button = document.getElementById(id);
  13.         if (button)
  14.             button.click();
  15.         return false;
  16.     }
  17.     else
  18.         return true;
  19. }

The submitonenter function activates the click associated with the button id if a keyboard event with code 13, the Enter key, has been detected.

Modify the views to add the calls the functions focusonenter and submitonenter:

  1. <div class="fields">
  2. <p class="label">What is your email address?</p>
  3. <p class="input"><input type="text" name="mailme_mail" id="mailme_mail" size="50" maxlength="100" title="Email" onkeypress="return focusonenter(event, 'mailme_subject')" value="<?php echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  4. <p class="info">Your email address is strickly confidential.</p>
  5. <p class="label">Type in the subject and the text of your message:</p>
  6. <p class="input"><input class="monospace" type="text" name="mailme_subject" id="mailme_subject" size="60" maxlength="100" title="Subject" onkeypress="return focusonenter(event, 'mailme_message')" value="<?php echo htmlspecialchars($subject, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  7. <p class="input"><textarea class="monospace" name="mailme_message" id="mailme_message" cols="70" rows="8" title="Text"><?php echo htmlspecialchars($message, ENT_COMPAT, 'UTF-8'); ?></textarea></p>
  8. <p class="input">
  9. <?php if ($with_captcha): ?>
  10. <img class="captcha" src="<?php echo $base_path; ?>/captcha" alt="" title="Verification code" />
  11. &nbsp;:&nbsp;
  12. <input type="text" name="mailme_code" id="mailme_code" size="4" maxlength="4" title="4 letters" onkeypress="return submitonenter(event, 'mailme_send')" value="" />
  13. <?php endif; ?>
  14. </p>
  15. <p class="submit"><button type="submit" name="mailme_send" id="mailme_send">Send</button></p>
  16. </div>

The attribute onkeypress="return focusonenter(event, 'mailme_subject')" of the field mailme_mail moves the focus to the field mailme_subject. Pressing Enter in the field mailme_subject moves to the field mailme_message. The attribute onkeypress="return submitonenter(event, 'mailme_send')" of the field mailme_code sends the form if the user presses the Enter key in the input field of the captcha.

Apply the same modifications to the French version:

  1. <div class="fields">
  2. <p class="label">Quelle est votre adresse d'email ?</p>
  3. <p class="input"><input type="text" name="mailme_mail" id="mailme_mail" size="50" maxlength="100" title="Email" onkeypress="return focusonenter(event, 'mailme_subject')" value="<?php echo htmlspecialchars($mail, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  4. <p class="info">Votre adresse d'email est strictement confidentielle.</p>
  5. <p class="label">Tapez le sujet et le texte de votre message&nbsp;:</p>
  6. <p class="input"><input class="monospace" type="text" name="mailme_subject" id="mailme_subject" size="60" maxlength="100" title="Sujet" onkeypress="return focusonenter(event, 'mailme_message')" value="<?php echo htmlspecialchars($subject, ENT_COMPAT, 'UTF-8'); ?>" /></p>
  7. <p class="input"><textarea class="monospace" name="mailme_message" id="mailme_message" cols="70" rows="8" title="Texte"><?php echo htmlspecialchars($message, ENT_COMPAT, 'UTF-8'); ?></textarea></p>
  8. <p class="input">
  9. <?php if ($with_captcha): ?>
  10. <img class="captcha" src="<?php echo $base_path; ?>/captcha" alt="" title="Code de vérification" />
  11. &nbsp;:&nbsp;
  12. <input type="text" name="mailme_code" id="mailme_code" size="4" maxlength="4" title="4 lettres" onkeypress="return submitonenter(event, 'mailme_send')" value="" />
  13. <?php endif; ?>
  14. </p>
  15. <p class="submit"><button type="submit" name="mailme_send" id="mailme_send">Envoyer</button></p>
  16. </div>

Add the loading of the file js/tools.js directly in the run function defined in the file library/engine.php:

  1.     head('javascript', 'tools');

Enter http://localhost/cms/site13/en/contact in the address bar of your navigator. Fill in the form validating the fields with Enter. Press Send. Come back in the history. Resend the form and check that it's not executed for a second time.

Set $with_captcha in blocks/mailme.php to true. Reload the form and fill it. Validate the verification code with Enter to send the form.

Comments

To add a comment, click here.