...

View Full Version : Need help understanding Double-Submittal Code



doubledee
09-01-2011, 05:54 AM
I have some code that I got that is supposed to prevent a form from being double-submitted and billing a customer twice.

Looking at the code, I'm not certain I understand how it works?! :confused:

Here it is...


<?php session_start(); ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
</head>

<body>
<div id="wrapper" class="clearfix">
<div id="inner">
<!-- Include BODY HEADER -->
<?php require_once(ROOT . 'components/body_header.inc.php'); ?>

<!-- PAYMENT FORM -->
<div id="paymentForm">
<?php
// Initialize variables.
$form_value = '';

// Check if Form value was set.
if (isset($_POST['form_value'])){
$form_value = $_POST['form_value'];
}

// Check if Form value was set in $_SESSION.
if (!isset($_SESSION['form_value'])){
$_SESSION['form_value'] = '';
}


// *********************************************************************
// HANDLE FORM.
// *********************************************************************
if (isset($_POST['submitted'])){
// Form was Submitted.

// Check for Double-Submittal.
if ($form_value == $_SESSION['form_value']){
// Initial Payment was Submitted.

// Check for Errors.
if (empty($errors)){
// PROCESS PAYMENT.

// Force new Unique ID to be assigned on Form Re-submit!!!
// unset($_SESSION['form_value']);

switch($response_array[0]){
case "1":
// Approved.
$responseMsg1 = "<p>Congratulations! Your transaction was successful.</p>
<p>Your Order Number is: '" . $invoiceNumber . "'</p>";

// Do not return to Payment Form!!!
exit();
}// End of CHECK FOR ERRORS.
}else{
// Form Double-Submitted!!
$responseMsg1 = "<p>Sorry! You have already submitted a payment.</p>";
$responseMsg2 = "<p>For your protection, this Payment Form has been disabled.</p>";

// Do not return to Payment Form!!!
exit();
}// End of CHECK FOR DOUBLE-SUBMITTAL.
}else{
// Drop through to Payment Form.

}// End of HANDLE FORM.
?>

<!-- NEW -->
<?php
// Create a Unique ID to be assigned to Form.
$_SESSION['form_value'] = md5(uniqid(rand(), true));
?>


<!-- @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -->
<!-- HTML PAYMENT FORM -->
<form id="payment" action="" method="post">

<!-- NEW -->
<!-- Hidden field used to store Form's Unique ID. -->
<input type="hidden" name="form_value" id="form_value"
value="<?php echo $_SESSION['form_value']; ?>" />

<!-- Submit Form -->
<fieldset id="submit">
<!-- Place Order button -->
<input name="submit" type="image" src="../buttons/PlaceOrder_black.png" value="Place Order" />
<input name="submitted" type="hidden" value="true" />
</fieldset>
</form>
</div><!-- End of PAYMENT FORM -->

</div><!-- End of #INNER -->
</div><!-- End of #WRAPPER -->
</body>
</html>


Questions:

1.) How is it that a Form can be re-submitted and create a double-posting in the first place?

2.) If you submit a form like this, and then the form is re-displayed with a Pass/Fail message, and then you hit the "Back" button on your browser and then the "Forward" button, what values appear in the $_POST on this second time displaying the form?

3.) I've read this code several times, and even though it works, I'm not seeing where the $form_value gets changed so that it does not match $_SESSION['form_value']?!

It seems like you never get back to this code...



<!-- NEW -->
<?php
// Create a Unique ID to be assigned to Form.
$_SESSION['form_value'] = md5(uniqid(rand(), true));
?>


...even if you hit the "Back" and then "Forward" browser buttons?! :confused:

Thanks,



Debbie

Rowsdower!
09-01-2011, 02:01 PM
When you go to a landing page from a method="post" form and hit your refresh button your browser will ask if you want to re-send the data (unless there was a redirect somewhere in the chain, in which case you won't get the option). That would be one way in which a user could "double-submit" the form without really meaning to.

Anyway, this script generates a random hash string, stores a copy in the $_SESSION array, and assigns the hash value to a hidden input named "form_value." When the form is submitted the script checks for $_POST['form_value'] having been set to a value equal to the copy stored in $_SESSION. If they match, then the submission is new and the script goes on to check for errors and attempt to process the payment.

If the values don't match, then a double-submit condition exists and the data is ignored and the form is rejected.

Then, whether rejected or accepted, the script generates a new random hash value and stores it in $_SESSION and in the form input named "form_value" and sets the form back up.

At this point, if the user was to hit "refresh" they would be asked if they want to re-send the data. If they say "yes" and the page gets the same data re-sent, they will be re-sending the OLD value of form_value and it will no longer match the copy stored in $_SESSION, hence the form will be rejected.

doubledee
09-01-2011, 05:21 PM
When you go to a landing page from a method="post" form and hit your refresh button your browser will ask if you want to re-send the data (unless there was a redirect somewhere in the chain, in which case you won't get the option). That would be one way in which a user could "double-submit" the form without really meaning to.

But most people who go "Back-Forward" will say "Yes, resubmit data" so they can go forward again.

Seems like a major issue on every web page in your website.



At this point, if the user was to hit "refresh" they would be asked if they want to re-send the data. If they say "yes" and the page gets the same data re-sent, they will be re-sending the OLD value of form_value and it will no longer match the copy stored in $_SESSION, hence the form will be rejected.

That is the part I don't get.

** Hard to explain here **

I stepped through my code in NetBeans a million times last night.

Let me try and describe where I am confused...

1.) Form loaded
- $_SESSION['form_value'] gets 123
- Hidden Field gets same value (123)
- Form displayed

2.) Form submitted
- $form_value gets 123 from Hidden Field
- $form_value (123) compared to SESSION (123)
- Transaction successful message displayed

3.) Hit "Back" - Form Re-Loaded
- $_SESSION['form_value'] gets 456
- Hidden Field should get same value (456)
- Form Re-displayed

4.) Hit "Forward" - Form Re-submitted (??)
- $form_value should get NEW 456 from Hidden Field


// Check if Form value was set.
if (isset($_POST['form_value'])){
$form_value = $_POST['form_value'];
}

- Would expect $form_value (to be 456 and not 123) compared to SESSION (456)
- Making Transaction successful message displayed


I see what is happening in NetBeans but not understanding the logic of why old $_POST values that I cannot see in NetBeans suddenly appear and are used?! :confused: :confused:


Debbie

Rowsdower!
09-01-2011, 06:37 PM
But most people who go "Back-Forward" will say "Yes, resubmit data" so they can go forward again.

Yes, and this is why this script exists. This is to prevent people from accidentally double/tripe/quadrupal/+++ paying if and when they do say "yes" to that question.


That is the part I don't get....

I will start off by saying that I have no idea what NetBeans is (sorry). But fortunately, that's not really important for us to talk about this.

Your BROWSER has cached the post data from your last submission in its memory (not the "temporary internet files" folder, but actually in the computer's RAM). So when you re-send the data to the server (as a result of refresh or back/forward navigation) your browser asks if you want to re-send the previous data. When you say "yes" your browser is sending the post data that it originally did despite the fact that your server's session data is now different for the form_value variable. Your browser doesn't know or care that the server's form_value has changed because it is just repeating its previous action. Think of it as copy/paste. Using the form's submit button is like copying. Then using the browser's re-send data thingy is like pasting. Even if you go back and see new text, you're still only going to paste again until you make another copy action. (does that analogy help?)

It doesn't matter if you use the "back" button on your browser and can see the exact same hash key there in the hidden input field as you saw the first time around or if it's an all-new key. Unless you click the submit button in the form you aren't sending any NEW data to the server. You are only going to be re-sending the exact same data as the last time and the key present in that data is only LOCAL TO YOU now. The server has already moved on with the $_SESSION variable to a new hash key. So when you click the browser's "forward" button, the hash key you send will not match the session's stored value on the server and the form submission will be blocked.

For further illustration, try changing the input type of "form_value" from "hidden" to "text." Then add print_r($_POST); just after session_start(); and go all through the steps you listed in your last post (steps 1-4). See which key it is that is still being passed in the post data. For more fun, edit the text field key to anything you like before hitting the browser's "forward" button. Did your change get submitted? No. Why? Because you didn't re-submit the form, you merely re-sent the OLD form data from the browser's memory.

doubledee
09-01-2011, 07:06 PM
Rowsdower,

Thanks for the thoughtful response!!



Yes, and this is why this script exists. This is to prevent people from accidentally double/tripe/quadrupal/+++ paying if and when they do say "yes" to that question.

It seems like you should incorporate this on every web page that has a form? Or maybe even more than that?



I will start off by saying that I have no idea what NetBeans is (sorry).

It is an IDE.



Your BROWSER has cached the post data from your last submission in its memory (not the "temporary internet files" folder, but actually in the computer's RAM). So when you re-send the data to the server (as a result of refresh or back/forward navigation) your browser asks if you want to re-send the previous data. When you say "yes" your browser is sending the post data that it originally did despite the fact that your server's session data is now different for the form_value variable.

This seems to be the key point here.



Your browser doesn't know or care that the server's form_value has changed because it is just repeating its previous action. Think of it as copy/paste. Using the form's submit button is like copying. Then using the browser's re-send data thingy is like pasting. Even if you go back and see new text, you're still only going to paste again until you make another copy action. (does that analogy help?)

Yes, that helps.



It doesn't matter if you use the "back" button on your browser and can see the exact same hash key there in the hidden input field as you saw the first time around or if it's an all-new key. Unless you click the submit button in the form you aren't sending any NEW data to the server. You are only going to be re-sending the exact same data as the last time and the key present in that data is only LOCAL TO YOU now.

Another key point!



The server has already moved on with the $_SESSION variable to a new hash key. So when you click the browser's "forward" button, the hash key you send will not match the session's stored value on the server and the form submission will be blocked.

What was throwing me off is that NetBeans showed...



// Check if Form value was set.
if (isset($_POST['form_value'])){
$form_value = $_POST['form_value'];
}


...this code above was evaluated as being TRUE so I just assumed that NEW data was being (or should be) sent?!



For further illustration, try changing the input type of "form_value" from "hidden" to "text." Then add print_r($_POST); just after session_start(); and go all through the steps you listed in your last post (steps 1-4). See which key it is that is still being passed in the post data.

Interesting manual technique, but my IDE does the same thing but in a better way.



For more fun, edit the text field key to anything you like before hitting the browser's "forward" button. Did your change get submitted? No. Why? Because you didn't re-submit the form, you merely re-sent the OLD form data from the browser's memory.

Okay, I think I get it now.

THANKS for helping me get this figured out in my head!!

Now the challenge is to figure out WHEN, WHERE, and WHY I need to use similar code on my website.

(I'm so fearful of something breaking or blowing up and me not even knowing that I should have written code to handle things... :( I know I started using this code after I found out that people could resubmit the Credit card Checkout Form a million times if I didn't prevent that!!)


Debbie

Rowsdower!
09-01-2011, 07:15 PM
It seems like you should incorporate this on every web page that has a form? Or maybe even more than that?

Well I touched on one other option in an earlier post I made in this thread. If you are using a redirect you won't have to worry about this problem at all.

In a case like that rather than submitting to a workflow like this:

Page A -> Page A

You use a workflow something like one of these:

Page A -> Processing Page -> Page A
Page A -> Processing Page -> Page B
Page A -> Processing Page -> Another Processing Page -> Page A
Page A -> Processing Page -> Another Processing Page -> Page B

And you get the idea. You post to an intermediary page which handles the payment (or database, or whatever you need done) but DOES NOT print anything to the browser, but instead uses a header('Location: Page A.html'); function (or wherever you want to send them) to redirect the user to another landing page, so that they land there WITHOUT using a post submission. Then if the user hits the "refresh" button or if they use the "back" button and the "forward" button they go right between Page A and whatever landing page they ultimately hit and never see the intermediary page(s), hence the browser never asks to re-send the data.

doubledee
09-01-2011, 07:31 PM
Well I touched on one other option in an earlier post I made in this thread. If you are using a redirect you won't have to worry about this problem at all.

In a case like that rather than submitting to a workflow like this:

Page A -> Page A

You use a workflow something like one of these:

Page A -> Processing Page -> Page A
Page A -> Processing Page -> Page B
Page A -> Processing Page -> Another Processing Page -> Page A
Page A -> Processing Page -> Another Processing Page -> Page B

And you get the idea. You post to an intermediary page which handles the payment (or database, or whatever you need done) but DOES NOT print anything to the browser, but instead uses a header('Location: Page A.html'); function (or wherever you want to send them) to redirect the user to another landing page, so that they land there WITHOUT using a post submission. Then if the user hits the "refresh" button or if they use the "back" button and the "forward" button they go right between Page A and whatever landing page they ultimately hit and never see the intermediary page(s), hence the browser never asks to re-send the data.

That sounds like a much more mature way to code a website...

How hard is it to design/code that way?

And how do you successfully and securely pass data between all of those pages? (I'm having that issue right now on a related topic.)



Debbie

Rowsdower!
09-01-2011, 07:52 PM
Coding that way isn't tough at all. I am in the habit of coding all of my processing at the top of the PHP file before any output is generated. The only PHP that occurs after the start of the HTML output is for displaying dynamic text, figures, and calculations that I have already made earlier in the script.

So for me, the only adjustment to make in a case like that is to cut the file off before the HTML output and use a redirect header to follow on to the next page, which contains everything that would have otherwise been below the first script. If there is any information that I need to pass to the final page - such as a success or failure message - I set it in the intermediary page using a $_SESSION variable (or log it in a database if I need to), then I use it in the landing page and unset that $_SESSION variable (or delete that database entry) right after printing it so that I don't forget to release it.

Other than that type of mild adjustment there really isn't anything special to worry about for page functionality.

As for security - I am the wrong source to ask. I have no background in security at all so I don't know what (if any) special things need to be done to secure this process that wouldn't need to be done for an A->A method.

doubledee
09-01-2011, 08:31 PM
Here I go again breaking something that was basically working... :rolleyes:

I would like to learn how to break up my MEGA-php pages that get re-submitted to themselves are are really several pages all-in-one including a mess of nested PHP and HTML?! :(

On simple pages like my Log In page, I have done a good job of putting 95% of the PHP first and then the HTML at the end. This is easy to do because things are straight-forward...


<?php
If form was submitted, check data.
If data is okay, look for user.
If user found, set Session variables and re-direct.
?>

<html>
display form here
</html>

Where things get much trickier is when I have something like my "Add a Comment" page - or even worse on my "Credit Card payment Processing" page - that first has to display the form, and then check the form data, and then if the comment was successfully submitted re-display the page with a message like "You comment was added." or a fail message like "A system error occurred."

The approach I've taken - which works, but is a maintenance nightmare - is as follows...

- Start off with HTML headers
- Create HTML Wrapper DIVs for page layout
- Switch to PHP to handle form data
- If no errors, echo HTML to display Success/Failure message which would fall in the middle of my HTML Wrapper DIVs above
- Close out this inserted HTML to close out HTML page
- If form is double-submitted, echo different HTML which would fall in the middle of my HTML Wrapper DIVs above
- Close out this inserted HTML to close out HTML page
- Have standard HTML Form code here which we get to if the form was not submitted.


So basically I have...


<html>
<head></head>
<body>
php code that displays Success/Fail HTML messages, then close out html prematurely.

php code that displays Double-Submittal Error message, then close out html prematurely.

html form if no POST exists
</body>
</html>


My "CC Payment Processing" script is 903 lines long... One big mess to manage!!!

If I could break things up, so there is a "add_comment.php" script to display the form and then a second page "add_comment_results.php" to display the outcome of the attempted submittal, that would be much easier to manage.

And if there could be a 3rd page in between those two, to do the PHP processing, maybe even better?!

How I can dissect these unwieldy pages and break them down remains to be seen.

If you can help me get started, I think my website would be much more 1.) Maintainable, 2.) Scalable, and 3.) Secure.

Thanks,


Debbie

tangoforce
09-01-2011, 08:32 PM
And how do you successfully and securely pass data between all of those pages? (I'm having that issue right now on a related topic.)


There are only 2 ways and we've told you this many times:
Sessions
Databases.

Sessions for short term use where users are almost certain to be following a logic flow (EG page1 leads to page2)

Databases for storage of long term data - EG where a user may go away and come back.

There is no other way plain and simple. What part of this continues to trouble you?

Rowsdower!
09-01-2011, 10:02 PM
OK, let's play nice everybody... No need to be rude.


As for learning "how" to separate your code, well, that's something you more or less have to just learn. It's like learning how to chew your food. It can't really be "taught," you just find out what works for you and go with it. It is going to be influenced by your own coding style so anything any one other coder does might not be right for you. Just find what works and what you can keep track of in your own mind. The important part is that YOU find it manageable/scalable.

The best thing I can recommend is to save a backup version of your site (database included) and put it somewhere safe so you can restore it if things get wrecked. Then... smash it up. Take it one page at a time and test with each new page to make sure it works after being realigned.

First I would take all "processing" blocks of code out of the body and move them up to the top of the page. You'll probably need to pay special attention to your if/else statements and any for/while loops to make sure that things are being processed correctly.

Then, for any files that get submitted, I would grab the relevant processing script from your existing page and paste it in to the new intermediary file so that ALL it does is process the form. Then, at the bottom of the intermediary file, use the header() function to redirect the user either back to the original page or else to your new landing page - whatever the case (and your preference) may be.

Really, the main thing is to try, try again. And if you break it, remember that there isn't anything that can't be fixed since you made a backup.

And try not to think of having more intermediary pages or more redirects as being better. Generally, you want as few stops as possible. The number of files is not important to anyone but the developer. It is the number of HTTP requests that will impact users, so I generally try not to redirect more than once.

I can also say that when I wanted a 1-file system for a form submission I have been known in the past to have a page submit a post form to itself, but at the very top of the script I have a form-handling section in an "if" statement while the rest of the page is in an "else" statement. Then the form-handling portion redirects the user back to the same page again when the processing is done and when redirected the user runs in the "else" statement and loads the page as normal. So you see, you can still do a redirect in an all-in-one file - if that is what you want to do. This could have easily been done with two files and the user wouldn't really know the difference. File count is irrelevant in that sense.

This is one place that you get to do it however you like.

tangoforce
09-01-2011, 10:10 PM
OK, let's play nice everybody... No need to be rude.

I'm not being rude, I'm being plain honest and saying it as it is. There are two ways to realistically pass data from one page to another.

djm0219
09-02-2011, 05:44 AM
My "CC Payment Processing" script is 903 lines long... One big mess to manage!!!

That's not totally out of line IMO though if it is a "mess" as you say then it would likely be worth it to "un mess" it :) I have a page for payments that is just over 400 lines long and does everything needed.

doubledee
09-02-2011, 05:47 AM
That's not totally out of line IMO though if it is a "mess" as you say then it would likely be worth it to "un mess" it :)

That's what you guys are here for - to help Debbie "un-mess stuff"?! :D



I have a page for payments that is just over 400 lines long and does everything needed.

Then you must be twice as efficient as me?! :p

Working on "add_comment2.php" as we speak...


Debbie

djm0219
09-02-2011, 06:10 AM
Then you must be twice as efficient as me?! :p

Naw, just been doing this programming stuff for WAY too long :D

doubledee
09-02-2011, 07:23 AM
Rowsdower,

If my "double-submission" issue is being caused because I am currently re-submitting my form/script onto itself, and the $_POST array is susceptible to issues when a user does "Back" and then "Forward", then can you get me started on how to re-write my "add_comment.php" script using re-directs?

I spent an hour or two tonight trying to use re-directs to just handle the different Success/Failure messages after the form is submitted, but then I realized that won't solve my "double-submission" issue?!

Normally I put my PHP first and if the form is submitted, then I check the form values and then handle the data.

But that is where I need to break things out into another file and I'm not sure how to do that.

Again, some hints would be helpful!

Thanks,


Debbie

Rowsdower!
09-02-2011, 01:53 PM
Show us what you have tried so far.

You should be two hours closer to the right answer and I don't want to work with outdated code.

Remember also that I described a situation wherein the script was all a one-file deal. You just submit to self, then redirect to self - basically. The important part is redirecting before the landing page because that will wipe the "post" slate clean. Where you redirect to is not important.

doubledee
09-03-2011, 03:11 AM
Show us what you have tried so far.

You should be two hours closer to the right answer and I don't want to work with outdated code.

I don't have anything to show because I am confused how to implement what you described. :(



Remember also that I described a situation wherein the script was all a one-file deal. You just submit to self, then redirect to self - basically. The important part is redirecting before the landing page because that will wipe the "post" slate clean. Where you redirect to is not important.

I'm not getting how that works.

I did spend this afternoon building a test form where I kept track of a Counter in the $_SESSION and $_POST. And what I determined - or got a better understanding of - is that once you populate and submit a form, those form values remain in $_POST until it cleaned out.

The only was you can change the $_POST array values is if you re-submit the form.

With my problem yesterday, if the user goes Back/Forward, every time they go Forward, the original $_POST values are being represented to your script, and in my case that means re-submitting the same Comments again and again.

(Still not sure why I am losing my "Article Title" and "User First Name" from the $_SESSION when things go Forward again, though?!)

--------

How am I supposed to break up my code?

1.) Do I perform an INSERT in the original form, then redirect to the "Processing Script" and just display the Pass/Fail message in the next page?


2.) Do I bypass the Submit button, and pass the form data to the "Processing Script", and let it do the INSERT and Pass/Fail message?


3.) Other?


Debbie

djm0219
09-03-2011, 01:00 PM
Take a peek at what I sent you Debbie. It implements what Rowsdower! has suggested.



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum