Index: openacs-4/packages/q-forms/tcl/form-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/q-forms/tcl/form-procs.tcl,v diff -u -N -r1.2 -r1.3 --- openacs-4/packages/q-forms/tcl/form-procs.tcl 8 Apr 2015 08:34:36 -0000 1.2 +++ openacs-4/packages/q-forms/tcl/form-procs.tcl 2 Jan 2017 10:30:48 -0000 1.3 @@ -2,9 +2,17 @@ routines for creating, managing input via html forms @creation-date 21 Nov 2010 - @cs-id $Id: + @Copyright (c) 2010-5 Benjamin Brink + @license GNU General Public License 2, see project home or http://www.gnu.org/licenses/gpl.html + @project home: http://github.com/tekbasse/q-forms + @address: po box 20, Marylhurst, OR 97036-0020 usa + @email: tekbasse@yahoo.com } +#agenda: +##code a proc called qf_bypass that does like qf_input hidden with sec_hash +## but emits no html tag to the form. + # use _ to clear a new default # use upvar to grab previous defaults and re-use (with qf_input only) # main namespace vars: @@ -14,6 +22,7 @@ # __form_ids_fieldset_open_list = list that contains form ids where a fieldset tag is open # __form_arr contains an array of forms. Each form built as a string by appending tags, indexed by form_id, for example __form_arr($id) # __qf_arr contains last attribute values of a tag (for all forms), indexed by {tag}_attribute, __form_last_id is in __qf_arr(form_id) +# __qf_hc_arr contains sh_key_id for each form_id # a blank id passed in anything other than qf_form assumes the current (most recent used form_id) # to fix: id for nonform tag should not be same as form_id. use an attribute "form_id" for assigning tags to specific forms. @@ -33,6 +42,9 @@ } { creates the form key for a more secure form transaction. Returns the security hash. See also qf_submit_key_accepted_p } { + upvar 1 __qf_hc_arr __qf_hc_arr + upvar 1 attributes_arr attributes_arr + # This proc is inspired from sec_random_token if { $instance_id eq "" } { # set instance_id package_id @@ -68,9 +80,11 @@ } append sec_hash_string $start_clicks $session_id $secure_p $client_ip $action_url $time_sec $key_id set sec_hash [ns_sha1 $sec_hash_string] + set sh_key_id [db_nextval qf_id_seq] db_dml qf_form_key_create {insert into qf_key_map - (instance_id,rendered_timestamp,sec_hash,key_id,session_id,action_url,secure_conn_p,client_ip) - values (:instance_id,:time_sec,:sec_hash,:key_id,:session_id,:action_url,:secure_p,:client_ip) } + (instance_id,sh_key_id,rendered_timestamp,sec_hash,key_id,session_id,action_url,secure_conn_p,client_ip) + values (:instance_id,:sh_key_id,:time_sec,:sec_hash,:key_id,:session_id,:action_url,:secure_p,:client_ip) } + set __qf_hc_arr($attributes_arr(form_id)) $sh_key_id return $sec_hash } @@ -80,6 +94,8 @@ } { Checks the form key against existing ones. Returns 1 if matches and unexpired, otherwise returns 0. } { + # sh_key_id is passed to qf_get_inputs_as_array to collect hidden name value pairs. + upvar 1 sh_key_id sh_key_id # This proc is inspired from sec_random_token if { $instance_id eq "" } { # set instance_id package_id @@ -104,19 +120,24 @@ } # the key_id is used to help generate unpredictable hashes, but isn't used at this level of input validation set accepted_p [db_0or1row qf_form_key_check_hash { - select session_id as session_id_i, action_url as action_url_i, secure_conn_p as secure_conn_p_i, client_ip as client_ip_i from qf_key_map - where instance_id =:instance_id and sec_hash =:sec_hash and submit_timestamp is null } ] + select session_id as session_id_i, action_url as action_url_i, secure_conn_p as secure_conn_p_i, client_ip as client_ip_i, sh_key_id + from qf_key_map + where instance_id=:instance_id + and sec_hash=:sec_hash + and submit_timestamp is null } ] if { !$accepted_p } { # there is nothing to compare. log current values: - ns_log Warning "qf_submit_key_accepted_p: is false. action_url '$action_url' sec_hash '$sec_hash'" + ns_log Warning "qf_submit_key_accepted_p.115: is false. action_url '${action_url}' sec_hash '${sec_hash}'" if { $connected_p } { - ns_log Warning "qf_submit_key_accepted_p: session_id '$session_id' secure_p '$secure_p' client_ip '$client_ip'" + ns_log Warning "qf_submit_key_accepted_p.117: session_id '${session_id}' secure_p '${secure_p}' client_ip '${client_ip}'" } } else { # Mark the key expired set submit_timestamp [ns_time] - db_dml qf_form_key_expire { update qf_key_map - set submit_timestamp = :submit_timestamp where instance_id =:instance_id and sec_hash =:sec_hash and submit_timestamp is null } + db_dml qf_form_key_expire { update qf_key_map set submit_timestamp=:submit_timestamp + where instance_id =:instance_id + and sec_hash=:sec_hash + and submit_timestamp is null } } return $accepted_p } @@ -143,7 +164,8 @@ set arg_arr(multiple_key_as_list) 0 set arg_arr(hash_check) 0 set arg_full_list [list duplicate_key_check multiple_key_as_list hash_check] - #set arg_list [list $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 ] + #set arg_list \[list $arg1 $arg2 $arg3 $arg4 $arg5 $arg6 \] + set instance_id [ad_conn package_id] # collect args if { [llength $arg1] > 1 && $value1 eq "" } { set arg_list $arg1 @@ -160,10 +182,10 @@ foreach {name value} $arg_list { set attribute_index [lsearch -exact $arg_full_list $name] if { $attribute_index > -1 } { - set arg_arr($name) $value + set arg_arr(${name}) $value } else { if { $name ne "" } { - ns_log Error "qf_get_inputs_as_array: '${name}' is not a valid name for use with args." + ns_log Error "qf_get_inputs_as_array.170: '${name}' is not a valid name for use with args." } } } @@ -176,21 +198,21 @@ } else { set __form_size [ns_set size $__form] } - #ns_log Notice "qf_get_inputs_as_array: formsize $__form_size" + #ns_log Notice "qf_get_inputs_as_array.183: formsize $__form_size" for { set __form_counter_i 0 } { $__form_counter_i < $__form_size } { incr __form_counter_i } { regexp -nocase -- {^[a-z][a-z0-9_\.\:\(\)]*} [ns_set key $__form $__form_counter_i] __form_key # Why doesn't work for regexp -nocase -- {^[a-z][a-z0-9_\.\:\(\)]*$ } ? set __form_key_exists [info exists __form_key] - # ns_log Notice "qf_get_inputs_as_array: __form_key_exists = ${__form_key_exists}" + # ns_log Notice "qf_get_inputs_as_array.189: __form_key_exists = ${__form_key_exists}" # no inserting tcl commands etc! if { $__form_key_exists == 0 || ( $__form_key_exists == 1 && [string length $__form_key] == 0 ) } { # let's make this an error for now, so we log any attempts - # ns_log Notice "qf_get_inputs_as_array: __form_key_exists ${__form_key_exists} length __form_key [string length ${__form_key}]" - # ns_log Notice "qf_get_inputs_as_array(ref156: attempt to insert unallowed characters to user input '{__form_key}' as '[ns_set key $__form $__form_counter_i]' for counter ${__form_counter_i}." + # ns_log Notice "qf_get_inputs_as_array.194: __form_key_exists ${__form_key_exists} length __form_key \[string length ${__form_key}\]" + # ns_log Notice "qf_get_inputs_as_array.196: attempt to insert unallowed characters to user input '{__form_key}' as '\[ns_set key $__form $__form_counter_i\]' for counter ${__form_counter_i}." if { $__form_counter_i > 0 } { - ns_log Notice "qf_get_inputs_as_array: attempt to insert unallowed characters to user input '{__form_key}'." + ns_log Notice "qf_get_inputs_as_array.197: attempt to insert unallowed characters to user input '{__form_key}'." } } else { set __form_key [ad_quotehtml $__form_key] @@ -202,52 +224,109 @@ set __form_input_exists 1 # check for duplicate key? - if { $arg_arr(duplicate_key_check) && [info exists __form_buffer_arr($__form_key) ] } { - if { $__form_input ne $__form_buffer_arr($__form_key) } { + if { $arg_arr(duplicate_key_check) && [info exists __form_buffer_arr(${__form_key}) ] } { + if { $__form_input ne $__form_buffer_arr(${__form_key}) } { # which one is correct? log error - ns_log Error "qf_get_form_input: form input error. duplcate key provided for ${__form_key}" + ns_log Error "qf_get_form_input.212: form input error. duplcate key provided for '${__form_key}'" ad_script_abort # set __form_input_exists to -1 instead of ad_script_abort? } else { - ns_log Warning "qf_get_form_input: notice, form has a duplicate key with multiple values containing same info.." + ns_log Warning "qf_get_form_input.216: notice, form has a duplicate key with multiple values containing same info.." } } elseif { $arg_arr(multiple_key_as_list) } { - ns_log Notice "qf_get_inputs_as_array: A key has been posted with multible values. Values assigned to the key as a list." - if { [llength $__form_buffer_arr($__form_key)] > 1 } { + ns_log Notice "qf_get_inputs_as_array.219: A key has been posted with multible values. Values assigned to the key as a list." + if { [llength $__form_buffer_arr(${__form_key})] > 1 } { # value is a list, lappend - lappend __form_buffer_arr($__form_key) $__form_input + lappend __form_buffer_arr(${__form_key}) $__form_input } else { # convert the key value to a list - set __value_one $__form_buffer_arr($__form_key) - unset __form_buffer_arr($__form_key) - set __form_buffer_arr($__form_key) [list $__value_one $__form_input] + set __value_one $__form_buffer_arr(${__form_key}) + unset __form_buffer_arr(${__form_key}) + set __form_buffer_arr(${__form_key}) [list $__value_one $__form_input] } } else { - set __form_buffer_arr($__form_key) $__form_input - # ns_log Debug "qf_get_inputs_as_array: set ${form_array_name}($__form_key) '${__form_input}'." + set __form_buffer_arr(${__form_key}) $__form_input + # ns_log Debug "qf_get_inputs_as_array.231: set ${form_array_name}($__form_key) '${__form_input}'." } # next key-value pair } + } - if { $arg_arr(hash_check) } { - if { [info exists __form_buffer_arr(qf_security_hash) ] } { - set accepted_p [qf_submit_key_accepted_p $__form_buffer_arr(qf_security_hash) ] - if { $accepted_p } { - unset __form_buffer_arr(qf_security_hash) - array set __form_input_arr [array get __form_buffer_arr] - return $__form_input_exists + if { $__form_input_exists } { + if { $arg_arr(hash_check) } { + if { [info exists __form_buffer_arr(qf_security_hash) ] } { + set accepted_p [qf_submit_key_accepted_p $__form_buffer_arr(qf_security_hash) ] + if { $accepted_p } { + + # Are there any hidden name pairs to grab from db? + set name_value_lists [db_list_of_lists qf_name_value_pairs_r {select arg_name,arg_value + from qf_name_value_pairs + where instance_id=:instance_id + and sh_key_id=:sh_key_id} ] + # clear any external input and warn if it is different + foreach pair_list $name_value_lists { + set __form_key [lindex $pair_list 0] + set __form_input [lindex $pair_list 1] + if { [info exists __form_buffer_arr(${__form_key}) ] } { + set test [ad_unquotehtml $__form_buffer_arr(${__form_key})] + if { $test ne $__form_input } { + ns_log Warning "qf_get_inputs_as_array.12000: input of type 'hidden' from form does not match for name '${__form_key}'. Internal used. internal '${__form_input}' from form '${test}'" + array unset __form_buffer_arr $__form_key + } + } + } + foreach pair_list $name_value_lists { + set __form_key [lindex $pair_list 0] + set __form_input [lindex $pair_list 1] + # For consistency, this is a repeat of external form logic checks above. + + # check for duplicate key? + if { $arg_arr(duplicate_key_check) && [info exists __form_buffer_arr(${__form_key}) ] } { + if { $__form_input ne $__form_buffer_arr(${__form_key}) } { + # which one is correct? log error + ns_log Error "qf_get_form_input.312: form input error. duplcate key provided for '${__form_key}'" + ad_script_abort + # set __form_input_exists to -1 instead of ad_script_abort? + } else { + ns_log Warning "qf_get_form_input.316: notice, form has a duplicate key with multiple values containing same info.." + } + } elseif { $arg_arr(multiple_key_as_list) } { + ns_log Notice "qf_get_inputs_as_array.319: A key has been posted with multible values. Values assigned to the key as a list." + if { [llength $__form_buffer_arr(${__form_key})] > 1 } { + # value is a list, lappend + lappend __form_buffer_arr(${__form_key}) $__form_input + } else { + # convert the key value to a list + set __value_one $__form_buffer_arr(${__form_key}) + unset __form_buffer_arr(${__form_key}) + set __form_buffer_arr(${__form_key}) [list $__value_one $__form_input] + } + } else { + set __form_buffer_arr(${__form_key}) $__form_input + # ns_log Debug "qf_get_inputs_as_array.231: set ${form_array_name}($__form_key) '${__form_input}'." + } + + # next key-value pair + } + + unset __form_buffer_arr(qf_security_hash) + array set __form_input_arr [array get __form_buffer_arr] + return $__form_input_exists + } else { + ns_log Notice "qf_get_inputs_as_array.346: hash_check with form input of '$__form_buffer_arr(qf_security_hash)' did not match." + return 0 + } } else { - ns_log Notice "qf_get_inputs_as_array: hash_check with form input of '$__form_buffer_arr(qf_security_hash)' did not match." + set accepted_p 0 + ns_log Notice "qf_get_inputs_as_array.351: hash_check requires qf_security_hash, but was not included with form input." return 0 } } else { - set accepted_p 0 - ns_log Notice "qf_get_inputs_as_array: hash_check requires qf_security_hash, but was not included with form input." - return 0 + array set __form_input_arr [array get __form_buffer_arr] + return $__form_input_exists } } else { - array set __form_input_arr [array get __form_buffer_arr] return $__form_input_exists } } @@ -270,16 +349,37 @@ {value1 ""} args } { - Initiates a form with form tag and supplied attributes. Returns an id. A clumsy url based id is provided if not passed (not recommended). + Initiates a form with form tag and supplied attributes. + Returns an id. + A clumsy url-based id is provided if not passed (not recommended). If hash_check passed, creates a hash to be checked on submit for server-client transaction continuity. +
+    To create a form that uploads a file, set attribute enctype to "multipart/form-data", set method to "post".
+    Also, create an input tag with type attribute  set to "file" to choose a file to upload, 
+    and set name attribute to name of file as it will be received at the server along with
+    other input from the form.
+
+    In the following example, name is set to "clientfile".
+    
+    After the form has been submitted, data can be retreived via ns_queryget (or qf_get_inputs_as_array ):
+
+    set uploaded_filename \[ns_queryget clientfile \]
+
+    set file_pathname_on_server \[ns_queryget clientfile.tmpfile \]
+    

+ For more info, see Naviserver documentation for ns_queryget

+ + @see qf_get_inputs_as_array } { # use upvar to set form content, set/change defaults # __qf_arr contains last attribute values of tag, indexed by {tag}_attribute, __form_last_id is in __qf_arr(form_id) + # __qf_hc_arr(form_id) contains value of hash_check. upvar 1 __form_ids_list __form_ids_list upvar 1 __form_arr __form_arr upvar 1 __form_ids_open_list __form_ids_open_list upvar 1 __qf_remember_attributes __qf_remember_attributes upvar 1 __qf_arr __qf_arr + upvar 1 __qf_hc_arr __qf_hc_arr # collect args if { [llength $arg1] > 1 && $value1 eq "" } { @@ -294,26 +394,31 @@ set arg_list [list ] } - set attributes_tag_list [list action class id method name style target title] + set attributes_tag_list [list action class id method name style target title encytype] set attributes_full_list $attributes_tag_list lappend attributes_full_list form_id hash_check key_id set attributes_list [list] foreach {attribute value} $arg_list { set attribute_index [lsearch -exact $attributes_full_list $attribute] if { $attribute_index > -1 } { - set attributes_arr($attribute) $value + set attributes_arr(${attribute}) $value if { [lsearch -exact $attributes_tag_list $attribute] > -1 } { lappend attributes_list $attribute } } else { - ns_log Error "qf_form: '${attribute}' is not a valid attribute." + ns_log Error "qf_form.337: '${attribute}' is not a valid attribute." } } if { ![info exists attributes_arr(method)] } { set attributes_arr(method) "post" lappend attributes_list "method" } + if { ![info exists attributes_arr(enctype)] && $attributes_arr(method) eq "post" } { + set attributes_arr(enctype) "application/x-www-form-urlencoded" + lappend attributes_list "enctype" + } + # if html5 should we default novalidate to novalidate? No for now. if { ![info exists __qf_remember_attributes] } { @@ -328,8 +433,8 @@ # use previous tag attribute values? if { $__qf_remember_attributes } { foreach attribute $attributes_list { - if { $attribute ne "id" && ![info exists attributes_arr($attribute)] && [info exists __qf_arr(form_$attribute)] } { - set attributes_arr($attribute) $__qf_arr(form_$attribute) + if { $attribute ne "id" && ![info exists attributes_arr(${attribute})] && [info exists __qf_arr(form_${attribute})] } { + set attributes_arr(${attribute}) $__qf_arr(form_${attribute}) } } } @@ -339,28 +444,30 @@ set id_exists [info exists attributes_arr(id) ] if { $id_exists == 0 || ( $id_exists == 1 && $attributes_arr(id) eq "" ) } { regsub {/} [ad_conn url] {-} form_key - append form_key "-[llength $__form_ids_list]" + append form_key "-[llength ${__form_ids_list}]" } else { # since a FORM id has to be unique, lets use it set form_key $attributes_arr(id) } set attributes_arr(form_id) $form_key - ns_log Notice "qf_form: generating form_id $attributes_arr(form_id)" + ns_log Notice "qf_form.380: generating form_id $attributes_arr(form_id)" } # prepare attributes to process set tag_attributes_list [list] foreach attribute $attributes_list { - set __qf_arr(form_$attribute) $attributes_arr($attribute) + set __qf_arr(form_${attribute}) $attributes_arr(${attribute}) # if a form tag requires an attribute, the following test needs to be forced true - if { $attributes_arr($attribute) ne "" } { - lappend tag_attributes_list $attribute $attributes_arr($attribute) + if { $attributes_arr(${attribute}) ne "" } { + lappend tag_attributes_list $attribute $attributes_arr(${attribute}) } } - set tag_html "" + set tag_html "" # set results __form_arr - append __form_arr($attributes_arr(form_id)) "$tag_html\n" + append __form_arr($attributes_arr(form_id)) ${tag_html} "\n" if { [lsearch $__form_ids_list $attributes_arr(form_id)] == -1 } { lappend __form_ids_list $attributes_arr(form_id) @@ -374,9 +481,15 @@ if { ![info exists attributes_arr(key_id) ] } { set attributes_arr(key_id) "" } - set tag_html "