Re: CGI Refresh question
by chromatic (Archbishop) on Jul 25, 2000 at 04:08 UTC
|
Hidden elements are pretty easy, and you're using CGI.pm, so getting at them is really easy. Of course, if you know how to make them, spoofing them is also easy, so be aware that this isn't sufficient security unless your users are completely ignorant of View Source.
# the HTML for making a hidden field
# <input type="hidden" name="submitted" value="1">
if (! defined param('submitted') ) {
# display a button
} else {
# don't display the button
}
To get around the refreshing bit is more difficult. Perhaps the default behavior would be *not* to increment the counter.
(Counters are inaccurate anyway, due to proxies, refreshes, and other things. I think you just picked it as an easy example, though.) | [reply] [d/l] |
|
|
Thank you chromatic, but as I said in my post, my sample code is a simplistic demo of a much more complicated script. Counting had nothing to do with it, it was just a way to make it simple. As for the insecurity of the hidden tag, I'm not using it for security, I just use it track which app this button talks to. In other words, imagine I was writting the script for n counters, and you could reset the one of your choice. I am using hidden data for that.
for( 1..5 )
{
print start_form(),
hidden( -name=>"cntrnum", -value=>$_ ),
submit( -name=>"action", -value=>"Reset Counter" ),
end_form(), "\n\n";
}
The real heart of the question is how to keep the user from
accidentally reseting the counter by refreshing.
(And again, counters are just an easy substitute for a bigger task).
And yes, I suspect that the users are ignorant of "View Source" which is why I'm so worried that they will refresh themselves into a bad spot. Y'see?
Thanks again! | [reply] [d/l] |
Re: CGI Refresh question
by turnstep (Parson) on Jul 25, 2000 at 05:17 UTC
|
#!perl
## Do the work here, process the form, etc. then:
print "Location: http://www.foobar.com/StaticWithFormStuff.html\n\n";
Refreshing brings up the "new" page, not the
script that directed them there.
A second way is to set the page as "expired" - some browsers will then complain if you try and refresh it.
A third way is to keep track of the page via a unique
URL or a hidden tag and store this info on the server
somewhere. If the unique tag comes up twice, you've got a
"repeat offender." Lots of overhead for the server, but
the most reliable solution, as you are relying on your
server to do the work, and not the unknown behavior of the
user's browser.
| [reply] [d/l] |
Re: CGI Refresh question
by tye (Sage) on Jul 25, 2000 at 05:46 UTC
|
Make the hidden parameter the timestamp when the web page
was generated. Have an empty, web-writable file for each
button on the page. When you get a button push, compare
the hidden field to the timestamp on that button's file.
If the time stamp is between that timestamp and "now", then
honor the request and "touch" that button's file (update
the file's timestamp). If the timestamp is too old, don't honor the request and respond saying that that resource
was recently change so their change was ignored. If the
timestamp is in the future, complain and "do something
appropriate".
Note that this also has the benefit of preventing me
from updating an item that my web page says should be updated but that Adam update while I was looking at my page. This is probably a good thing.
| [reply] |
|
|
Hang on, thereīs a trap you might fall into. Donīt forget,
that multiple users might call the same cgi at the
same time! That will mess up your time stamp files!
To make things worse, since HTTP is stateless, you have
to write a complete session management yourself if you
need one (can be done with the hidden fields mentioned,
among others). The initial description suggests (at least
to me) that you need such a "session concept".
Andreas
| [reply] |
|
|
Yes, there is a race condition where multiple clients can reset the same counter in rapid succession and both succeed. However, this race doesn't affect the requested purpose, that is, people will not be allowed to reset a count by doing a refresh/reload of a page. That is mostly why I didn't mention the race condition originally.
A more important flaw (to my eye), is that the solution doesn't distinguish between refusing my reset request because someone else beat me to it or because I resent a stale POST.
The "right" way to remove the race condition is to lock the timestamp file across the test and set. However, that wouldn't fix the bigger flaw. My subconscious has been wondering if there was a (fairly) simple trick for solving both problems (like append the process ID/thread ID/client address&port to the time stamp file, check if you were first, etc.). This type of problem often looks simple but usually isn't. Anyway, I never came up with one.
So lock the file, check the file's last modified time, and then either rewrite it with a unique ID that you also include hidden in each form or read the ID to determine which error to report, then unlock the file.
| [reply] |
(CMonster: use the redirect) RE: CGI Refresh question
by CMonster (Scribe) on Jul 25, 2000 at 18:40 UTC
|
I have to agree with turnstep's first suggestion,
since that's the way I code CGI in general. When doing any
real work behind the scenes, use a script that redirects
the user to a static version of the output to avoid the
refresh problem.
In your case, if you're interested in having just one
script, make it callable in the default way (which just
displays the current value of the counter), the incrementor
way (which increments the counter then redirects to the
default way), and the reset way (which resets and redirects).
Here's a simple example:
#!perl -w
use strict;
use CGI qw( :standard );
use CGI::Carp qw( fatalsToBrowser );
$|=1;
my $count = 0;
if( -e 'cnt.tmp') and not param('Start Over') )
{
open COUNT, 'cnt.tmp' or die "Failed to open cnt.tmp, $!\n";
while(<COUNT>)
{
if( /^\s*(\d+)\s*$/ )
{
$count = $1;
last
}
}
close COUNT or die "Failed to close cnt.tmp, $!\n";
}
if (param('Again') or param('Start Over')
{
open COUNT, '>cnt.tmp' or die "Failed to open >cnt.tmp, $!\n";
print COUNT ++$count;
close COUNT or die "Failed to close >cnt.tmp, $!\n";
print "Location: this.cgi\n\n";
}
else
{
print header, start_html('Simple Counter'), "\n";
print start_form(),
"Count equals $count",
br(), "\n",
submit( -name=>"Again" -value=>"Again"),
submit( -name=>"Start Over", -value=>"Start Over" ),
end_form(), "\n";
print end_html();
}
| [reply] [d/l] |
RE: CGI Refresh question
by Adam (Vicar) on Jul 25, 2000 at 22:41 UTC
|
I'm handling this with a redirect, the only thing I don't like is that it puts the paramater on the url line of the browser. Not too big a deal, just not asthetically (sp?)pleasing. if( param('Start Over') )
{
open COUNT, '>cnt.tmp' or die "Failed to open >cnt.tmp, $!\n";
print COUNT '0';
close COUNT or die "Failed to close >cnt.tmp, $!\n";
print redirect( -location=>url() . "?HiddenTag=$HiddenValue" );
}
And that works fine. Thanks for the help all! (And if anyone knows how to get that param through the redirect without putting it on the url line, let me know!) | [reply] [d/l] |
|
|
aesthetically or esthetically.
hey, you asked! :)
-- ar0n
| [reply] |
|
|
I've never found a way, but you can make your script
name very similar to the static page name, and use POST
instead of GET to minimize differences in appearance. Having your cgi scripts run from sonewhere other than
"cgi-bin" helps as well.
| [reply] |