Navigate
Home
ArticleWiki
Forum
Journal
Search
Newsletter
Links
Tech News
expertsrt.com
Welcome Guest.
Username:

Password:

Remember me

Avoiding a form being sent twice by mistake
Welcome, Guest. Please login or register.
December 01, 2008, 09:02:48 PM
11304 Posts in 1248 Topics by 496 Members
Latest Member: teentiodo
Experts Round Table Network  |  Serverside Technology  |  PHP  |  Avoiding a form being sent twice by mistake « previous next »
Pages: [1]
Author Topic: Avoiding a form being sent twice by mistake  (Read 558 times)
Esopo
Governing Council Member
*
Offline Offline

Posts: 74


WWW
« on: January 07, 2006, 02:35:55 AM »

The first thread in the PHP TA. What a great honor.

On to the good stuff,

I often use single-page scripts for small needs. For example, a small guestbook page.

When a user adds a post the form is sent to the same page, the information is added to the DB and the page reloaded (by headers) before displaying it to the visitor, so that the post variables are erased to make sure they won't send them twice by mistake (like refreshing the page).

It works very well, but I sense that there must be a different (better) method to achieve the same result.

I heard that something like this may do the trick:
Code:
header("Cache-Control: no-cache, must-revalidate");


But I am not familiar with the way it works.
Logged
nicholassolutions
Administrator
*
Offline Offline

Posts: 133



WWW
« Reply #1 on: January 11, 2006, 01:31:17 PM »

I suppose you could use sessions:

Code:

//at the top of your script
session_start();
if($_SESSION['processed'] === true) $_POST = array();

//..the rest of your script....

//part where you process the $_POST data
if(!empty($_POST['guestname'])){
   //do something with the data....
   $_SESSION['processed'] = true;
}


If you're inserting the data in a db, another option is to use some sort of 'checksum.' Add an extra column to your table,  checksum, and generate some random code appended to mktime() (on the off chance you generate the same code twice, you're not going to do it at exactly the same time, so this ensures each checksum is unique) that you include as a hidden field in the form. Then, insert this value into the checksum column when you make the database entry. When inserting the data, run a quick check to make sure there is no row where the checksum is equal to the one you have generated (if there is, it's a resubmission of your form), and insert the data accordingly (i.e. don't insert it if the checksum is already there). To make things go more quickly, it might help to only search within a subset of, say, the last 300 records, depending on how big your table is.

Of the two options, I tend to like the session solution from a programmer's point of view. From a practical point of view, the checksum solution might be better, since you don't have to deal with any of the problems you may encounter from using sessions. Even if you're not using a db, you could still use the idea of the checksum to prevent repeated processing, by storing just the checksums one-per-line in a plain old text file that you search when the form is submitted (you might want to clear it every day or so to keep the search from taking too long).


Matt
Logged
Anonymous
Guest
« Reply #2 on: January 12, 2006, 02:46:26 AM »

A pretty good way of quick way of generating a checksum is to serialize the array of valid columns and then do an md5 on the resultant string.

e.g.

Code:
<?php
function makeValid(array &$am_TestArray, $s_TestElement, $s_TestAs = 'string', $m_TestParameters = NULL)
{
// Invalid data results in an empty string, but the return type will be dependent upon the element's type.
$m_Result = '';

// Does the required element exist within the array?
if( isset( $am_TestArray[$s_TestElement] ) )
{
$m_TestValue = $am_TestArray[$s_TestElement];
switch( $s_TestAs )
{
case 'boolean' : // May be '0', '1', 'on', 'off', 'true', 'false'
if( ( '0' == $m_TestValue ) || ( 0 == strcasecmp( $m_TestValue, 'off') ) || ( 0 == strcasecmp( $m_TestValue, 'false') ) )
{
$m_Result = False;
}
if( ( '1' == $m_TestValue ) || ( 0 == strcasecmp( $m_TestValue, 'on') ) || ( 0 == strcasecmp( $m_TestValue, 'true') ) )
{
$m_Result = True;
}
break;
default :
$m_Result = htmlentities( $m_TestValue );
break;

}
}

return $m_Result;
}

$as_ValidatedPOST = array
(
'Name' => makeValid($_POST, 'Name'),
'Comment' => makeValid($_POST, 'Comment'),
'ReceiveMessage' => makeValid($_POST, 'ReceiveMessage', 'boolean'),
);

$s_Checksum = md5( serialize( $as_ValidatedPOST ) );
echo $s_Checksum;
?>
Logged
VGR
Mentor

Offline Offline

Posts: 682



WWW
« Reply #3 on: January 28, 2006, 07:14:14 AM »

Hi (and hello Richard, it's been a long time ! ;-)

I feel there is no such "better" solution than the one you use (header("Location:") after inserting in DB etc).

As there is no drawback to that solution, and effectively, as you wrote, it works well, I wouldn't even research an other solution.

I would especially NOT rely on headers about the cache-control, they are too unreliable (or if you prefer, a lot of proxies, cache engines, browsers and intermediate devices can simply not honore them, not to say anything about search engines' caches)

about the checksum in the DB, it doesn't simplify your script that much, and it is time consuming for achieving the same result as the one you currently have ;-)

best regards
Logged

techie overlord, answers all kind of questions on http://www.europeanexperts.org
Esopo
Governing Council Member
*
Offline Offline

Posts: 74


WWW
« Reply #4 on: January 28, 2006, 08:50:10 AM »

Thanks for your opinions. I was comfortable with my method but had that itch that there might be a better one. Now I feel better, and it is good to see other options that hand't occurred to me.
Logged
Pages: [1]
« previous next »
    Jump to: