Skip to content
This repository


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: RELEASE_2_52
Fetching contributors…


Cannot retrieve contributors at this time

file 320 lines (231 sloc) 12.987 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
Nutshell of using formkeys


These are all limits per the var 'formkey_timeframe' (seconds)

* max_<formname>_allowed - maximum submissions of a <formname> allowed

* max_<formname>_unusedfk - maximum number of unused formkeys for
<formname> allowed

* <formname>_speed_limit - minimum interval between postings of <formname>

* <formname>_response_limit - minimum interval between access
(creation of formkey) to submission (updating formkey value) of <formname>

* max_<formname>_viewings - maximum number of accesses of a form allowed
(just accesses, not posts)

Switch values in formkeyErrors

* usedform - unable to increment formkey value because
a. the value has been incremented already (and 'interval' is set)
b. a race condition exists where the user somehow got pass the maxposts
check and interval check

* invalid - the formkey is invalid because they are trying to submit with
a key that hasn't even been issued

* <formname>_maxreads - the user has reached their max_<formname>_viewings
and is no longer permitted to access <formname> within formkey_timeframe

* <formname>_maxposts - the user has reached their max_<formname>_allowed
and is no longer permitted to submit <formname> within formkey_timeframe

* <formname>_unused - the user has reached thier max_<formname>_unusedfk
and has created too many formkeys that they have never submitted <formname>

* <formname>_response - the user has tried to submit <formname> too soon
after replying (when the formkey was created)

* <formname>_speed - the user has tried to submit <formname> too soon
after submitting <formname> successfully.

Formabuse reasons (set in $abuse_reasons, no need to add when implementing
formkey code in a script):

* formabuse_invalid - message for 'abusers' table when 'invalid'

* formabuse_usedform - message for 'abusers' table when 'usedform'


You have a script, and it does different things. Take something like comments.
Comments has an editComments function, a submitCommentsFunction.

1. You hit 'reply' to a comment on the site. The code calls editComment
(this is an empty comment form) You want to check first if they haven't
exceeeded their max posts first, so these checks have to be run:

I. $error_flag =
formkeyHandler('max_post_check', 'comments', $formkeyid, $formkey)

* The var you need for this to work is max_<formname>_allowed where
<formname> in this example would be 'comments'.

If this check passes, you need to generate a
formkey, so you will need to

II. $error_flag =
formkeyHandler('generate_formkey', 'comments', $formkeyid, $formkey);

* The vars you need for this to work are:
max_<formname>_allowed, max_<formname>_unusedfk, <formname>_speed_limit

So now $form->{formkey} is set, and in the current form the user is
entering a story into, and there is a record in the database with this
formkey, but the value is 0 (not used yet)

If there is no error condition, the call editComment.

2. Next, the user could submit 'preview', in which case, you'd want to check max posts
again, just as you did with 'reply' (any time the user submits anything, it should
be checked), but another thing you need to do, in case the user logs in to post
the comment under thier uid (and this only happens with preview) is call
I. formkeyHandler('update_formkeyid', 'comments', $formkeyid, $formkey);

If there isn't an error condition, call editComment.

3. The user could have also submitted 'submit', in which case, you perform the folling
I. $error_flag =
formkeyHandler('max_post_check', 'comments', $formkeyid, $formkey);
II. $error_flag =
formkeyHandler('valid_check', 'comments', $formkeyid, $formkey);

'valid_check' checks whether the formkey exists (if the user is trying to use
a concocted formkey)

III. $error_flag =
formkeyHandler('interval_check', 'comments', $formkeyid, $formkey);

* The var you need for this to work is <formname>_speed_limit

'interval_check' checks the interval of the last successful submission of the comments

IV. $error_flag =
formkeyHandler('response_check', 'comments', $formkeyid, $formkey);

* The var you need for this to work is <formname>_response_limit

'response_check' checks the interval between 'reply' and 'submit'. This isn't
necessarily check you'd want to use on other forms.

V. $error_flag =
formkeyHandler('formkey_check', 'comments', $formkeyid, $formkey);

* The vars you need for this to work are:
max_<formname>_allowed, <formname>_speed_limit

This check is the check that updates the formkey value, and if it doesn't occur,
it could be one of two reasons: the formkey has been used already (hitting the
'back' button and submitting) or there is a race condition that the other
checks didn't catch.

If 'formkey_check' doesn't produce an error, set a condtion that says this
formkey can be updated after you call submitComment. Then, submitComment can be called.

NOTE: if the sub you call calls the form sub after it submits, you have to
have a formkey, and this is done by:

$error_flag = formkeyHandler('regen_formkey', 'comments', $formkeyid, $formkey);

This ensures that the form method your calling will have a new formkey. Look at createDiscussion

3. Finally... if submitComment succeeded (that is, if there weren't other errors
such as filter errors, bad input errors, duplicate comment errors) and if 'formkey_check'
didn't produce an error and set the update condition, then run

$slashdb->updateFormkey($formkey, $field_length);

If the sub you call produces and error (if submitComment fails) then you:


Otherwise, the formkey will fail when you fix whatever submitComment was
complaining about and submit again.

'$field_length' can be the length of your primary field that you're submitting, in
this example, $form->{postercomment}.

Final note: there is a fifth parameter you can pass formkeyHandler, as in the case with, when the opcode is 'saveuser'. The formkey checks have to be run, but you
can print anything yet because 'header' hasn't been called, so what you can do is:

$error_flag = formkeyHandler($check, $formname, $formkeyid, $formkey, \$note);

Getting this all working

In comments, the opcode data structure is used, having a list of checks to perform in
an array for each opcode. Loop through the list for each op:

my $ops = {
submit => {
                        function => \&submitComment,
                        seclev => 0,
                        post => 1,
                        formname => 'comments',
                        checks =>
                        [ qw ( max_post_check valid_check interval_check response_check
                                formkey_check ) ],

If the opcode is 'submit' :

for my $check (@{$ops->{$op}{checks}}) {

# NOTE this is where you can set the update_formkey condition
                        $ops->{$op}{update_formkey} = 1 if $check eq 'formkey_check';
                        my $formname = $ops->{$op}{formname};
                        $error_flag = formkeyHandler($check, $formname, $formkeyid, $formkey);

                        last if $error_flag;

Then you run your op:

if (! $error_flag) {
my $retval = $ops->{$op}{function}->($form, $slashdb, $user, $constants, $formkeyid);

if($retval) {
$slashdb->updateFormkey($formkey, $field_length);
} else {


More detailed descriptions

The formkey checks are handled by formkeyHandler. Formkey handler
is called:
$error_flag =
formkeyHandler($check, $formname, $formkeyid, $formkey);

example of what the args would actually be:

formkeyHandler('max_post_check','comments', 3, 'xd2i2u3o2u45');

in the case such as in users, where you have to check the formkey
on 'savepasswd', which happens before "header" is called, you need
to save the error message into "$note"

$error_flag =
formkeyHandler($check, $formname, $formkeyid, $formkey, \$note);
This way, since '$note' is being passwd, note gets the error message from the
formkey checks (if there's and error) but won't print the error, which happens
by default without that 5th argument.

formkeyHandler Checks:

generate_formkey - generates the formkey by populating $form->{formkey}
which automagically shows up in the form.
Before a formkey is created, formkeyHandler checks how many unused
formkeys (<formname>_unusedfk in vars, formkeyErrors message
<formname>_unused ) there are (formkeys that have resulted from a
user accessing a form but never submitting, potentially be collecting
formkeys to use to flood (over the period, default 4 hours). If this
number has been exceeded, then the error flag is incremented,
error message generated. Next, formkeyHandlers checks the interval
of when the last formkey was generated, if this is a 'generate_formkey'
call ('regen_formkey' would mean that a submission call is done and is
calling a form call, which doesn't need to be checked for the interval).
If this is smaller than the <formname>_speed_limit, then the $error_flag
is incremented. Finally, if the error_flag isn't set, createFormkey is
called. createFormkey gets the last submitted time stamp and last
idcount of a successful formkey for the user, populates form->{formkey}
with the formkey generated by calling getFormkey, and then inserts
the new formkey into the formkeys table.

max_read_check - checks how many times the form has been access
and returns and error if that number has been exceded
calls checkMaxReads
the var for the form is max_<formname>_viewings (just make sure it matches
the message in formkeyErrors is triggered by '<formname>_maxreads'

max_post_check - checks how many times formkeyid has successfully
posted and returns and error if that number has been exceded
calls checkMaxPosts
the var for the form is max_<formname>_allowed (just make sure it matches
the message in formkeyErrors is triggered by '<formname>_maxposts'

interval_check - checks the interval between last successful post
calls checkPostInterval
the var for the form is <forname>_speed_limit
(check hashref in checkPostInterval) to make sure
the message in formkeyErrors is triggered by '<formname>_speed'

response_check - check the response between reply and post (only
used on comments so far)
calls checkResponseTime
the var is <formname>_response_limit (check hashref in checkResponseTime)
the message in formkeyErrors is triggered by '<formname>_response'

valid_check - checks whether a formkey is valid
calls validFormkey
not form specific, no var
the message in formkeyErrors is triggered by 'invalid', no need to add
another message per form. Note: This is a check that if it fails, is
logged to the 'abusers' table because it's set in $abuse_reasons->{key}.

formkey_check - updates the formkey val to indicate the formkey has
been used, if the val is 0, if the idcount is less than <formname>_max_posts,
and if the last_ts value (last time updateFormkeyVal updated a formkey
for this user), then the value is updated. If there is an error, it either
failed because the value couldn't be incremented to 1 because it was already
1 which means the formkey was already used (which errorMessage then gets the
time stamp of when it was used), or the interval and/or the idcount values in
the where clause caused it to not increment, and there isn't a time stamp since
it wasn't updated. In formkeyErrors, it gives two messages for this based upon
whether [% interval %], is set or not. If interval is not set, "there was an
error submitting the form" is generated, otherwise,
"this form was used [% interval %] minutes ago" is generated.
The message in formkeyErrors is triggered by 'usedform' no need to add
another message per form. Note: This is a check that if it fails, is
logged to the 'abusers' table because it's set in $abuse_reasons->{key}.

regen_formkey - creates a new formkey in the case with functions that
regenerate a form after submitting (without going through the op hashref)
just need to check the calling function and see if it generates a new form
outside the op hashref
calls createFormkey which populates $form->{formkey}

generate_formkey - creates a new formkey. make sure this is the last call
if your checking max_post_check, update_formkeyid
calls createFormkey which populates $form->{formkey}
if you want the formkey in the form, you'll need to put
<INPUT TYPE="HIDDEN" NAME="FORMKEY" VALUE="[% form.formkey %]">
in the template for the form that you want it to be in

update_formkeyid - some forms require the formkey id to be updated
as in the case with comments where a user might reply as anon and
then log in and then post
calls updateFormkeyID
Something went wrong with that request. Please try again.