fmk has asked for the wisdom of the Perl Monks concerning the following question:

Hi monks, While im not able to use the $q->redirect($q->url()) methode, i have written a small script which creates a token, sends it per hidden-tag, and saves it in a session. On a reload, the session value and the POST-data value are the same, and the script prints an error message. This works quite good, except if you hit the submit-button realy realy fast serveral times in a row -than it complains about a reload which never happend. Does anybody know why this happens?
#!/usr/bin/perl use strict; use warnings; use CGI; use CGI::Session; use CGI::Carp qw(fatalsToBrowser); use Time::HiRes qw(gettimeofday); use Digest::SHA qw(sha256_hex); use HTML::Template::Compiled; my $q = new CGI(); my $token = uniqid(); my $s = new CGI::Session; my $t = HTML::Template::Compiled->new(path => [ '.' ], filename => 'test.tmpl', open_mode => ':utf8', tagstyle => [ '+tt' ], defa +ult_escape => 'HTML_ALL'); $t->param(token => $token); print $s->header(), $t->output; check_reload(); $s->param(token => $q->param('token')); sub check_reload { if( $q->param('token') && ($s->param('token') eq $q->param('token' +))) { print "Error: RELOAD"; } } sub uniqid { my($s,$us)=gettimeofday(); my($v)=sprintf("%06d%5d%06d",$us,substr($s,-5),$$); $v = sha256_hex($v); return $v; }
test.tmpl:
<form action="test.pl" method="post"> <input type="hidden" name="token" value="[%= token%]"> <input type="submit" value="Submit"> </form>
Regars,

Replies are listed 'Best First'.
Re: (CGI) Prevent a reload from resubmitting a form
by Utilitarian (Vicar) on Jun 27, 2009 at 10:35 UTC
    The second click could be calling the CGI script again within the limits of the gettimeofday() function. While you could improve this by increasing the granularity of the function, this would remain unscalable, you should add a random element to $v which would allow the session token to be unique, and also from a security perspective to make the session token less predictable.

    CGI::Session may offer what you require, or Apache::Session::Generate::ModUniqueId

    Update:Good observation by ww. In my haste I missed the presence of Time::HiRes.

    Could you retry with verbose logging of the uniquid() and check_reload() functions? (This may of course prevent a race condition from occuring, or it may demonstrate the issue,)

    The fact that you've tried adding a random element to $v without resolution troubles my understanding :(

      The granularity of gettimeofday() is microseconds. One would need very fast fingers to "hit the submit-button realy realy fast serveral times in a row..."
      I use CGI::Session to create the token which identifies the session, this is not the $token which should prevent the resend of POST-Data. I tired to add a rand(10) to $v but this didnt change the scripts behavior.
      Sorry, what do you mean with verbose logging?
        print the parameters your script is called with, the values your token is generated with etc.. to a logfile.

        This might show up where the problem arises (param{token} being null for $s and $q for example)

Re: (CGI) Prevent a reload from resubmitting a form
by mzedeler (Pilgrim) on Jun 27, 2009 at 12:52 UTC

    I don't quite see why you want to prevent the user from resubmitting a form in the first place. Maybe you want to ensure that some operation is only carried out once during the session, but that is something different.

    If the underlying problem really is that you carry out some operation, that should be carried out just once, use a server side transaction mechanism to ensure that two concurrent requests won't be able to carry out the operation at the same time, and some mechanism to skip the operation on subsequent requests.

    By doing it this way, your web application will be more user friendly by not punishing the user for reloading the page.

      The problem is that, if somebody hits F5 / Strg+R in the Browser, the Data will be send again and also submitted again into the database. I found two solutions for this on the net, the first is to redirect the user after the submit ( via $q->redirect($q->url()) ), the second is to check if a reload was done, this is what im trying to do.

        Don't try to detect a reload. Read what mzedeler wrote: Implement transactions. Include a unique identifier in the submitted form and refuse to process the submitted form again if you see that identifier more than once.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: (CGI) Prevent a reload from resubmitting a form
by ww (Archbishop) on Jun 27, 2009 at 10:47 UTC
    This may be way off base, but unless the server where your script is running is local, you may need to consider latency, server-response-time and other network issues.
      The server runs local, there should be no network issues :/.
        It's not just network lag. There is a certain amount of latency involved in any HTTP request, even against a local server. Especially if you're using vanilla CGI, which has to load up the perl binary and compile your code before actually handling each request.

        In my experience developing vanilla CGI against local servers, this can easily add up to a total delay of anywhere from a noticeable fraction of a second up to a couple of seconds between when you click and when the new page is rendered. If the user clicks multiple times before the new page is rendered, then the old page (with the old token value) will be submitted multiple times and there is nothing you can do on the server side to prevent that, only to detect when it happens and try to deal with it in a sane manner.

        I have seen a few sites which try to address this on the client side by putting a little javascript on the submit button which disables it after it's clicked the first time, but I'm not aware of any other way to prevent multiple submits from occurring. Fortunately, in actual practice, it generally doesn't seem to be a terribly significant issue.

Re: (CGI) Prevent a reload from resubmitting a form
by Herkum (Parson) on Jun 27, 2009 at 14:35 UTC

    I don't know why no one has suggested this but you could use Javascript to disable the submit button. Technically they could still do multiple submits by ignoring/disabling the Javascript but that does not appear to be your real concern at the moment.

      When developing a webapp, never trust user submitted data or depend on Javascript, they may be running noScript, they may be using Lynx (I do regularly), you can test if JS is enabled by having a script on the page submit a js=true parameter and provide a js enabled page, but don't presume it's running
      This does not help against a Browser RELOAD.

        Your token tracking does not really prevent a user from 'RELOAD' either. I can just randomly make up a token value and resubmit the same data again and again because you are only validating whether that token has already been submitted.

        You will always to validate your data on the back-end regardless because someone can ALWAYS submit data against your script with random inputs.

        Your original question was how do I prevent a user from pressing the button quickly and getting an accidental reload, well JavaScript can allow you to do this, as well as a Modal (look up jquery SimpleModal).