(part of the ArsDigita Community System Developer's guide)
Incorrect:Complex case relevant to ACS:
ns_write "<a href=\"index.tcl?foo=$foo\">ACS</a>"
If foo contains a space, ?, & or another character not valid in an URL parameter, foo will not be defined correctly on the next page.Solutions:
Usens_urlencode
to protect an individual string.
ns_write "<a href=\"index.tcl?foo=[ns_urlencode $foo]\">ACS</a>"
Useexport_url_vars
to handle one or more url parameters (export_url_vars
also filters out undefined variables).
ns_write "<a href=\"index.tcl?[export_url_vars foo bar]\">ACS</a>"
return_url
parameter to pass a URL through the registration process, both the individual parameters of return_url and the return_url as whole must be urlencoded.
Incorrect:
ns_returnredirect "/register/index.tcl?return_url=[ns_urlencode "index.tcl?domain=$domain"]"
Correct:
ns_returnredirect "/register/index.tcl?return_url=[ns_urlencode "index.tcl?[export_url_vars domain]"]"
Incorrect:
ns_write "<input type=text name=test value=$foo>"
If foo contains a > or ", the value may be cropped or your form may be misdefined.Correct:
ns_write "<input type=text name=test value=\"[philg_quote_double_quotes $foo]\">"For text fields, the
export_form_value
is useful.
ns_write "<input type=text name=test [export_form_value foo]>"One or more hidden variables can be properly output and protected using export_form_vars.
ns_write "[export_form_vars foo bar]"
ns_quotehtml
will do this for you.
ns_write "<textarea>[ns_quotehtml $foo]</textarea>"
The ' is SQL's escape character.Incorrect:
ns_db dml $db "insert into foo (string_column) values ('$string_column')"Solutions:
If you useset_form_variables_string_trim_DoubleApos
orset_the_usual_form_variables
to define variables for the keys/value pairs in [ns_conn form], quoted forms of the variables will be defined.Correct:
set_the_usual_form_variables
ns_db dml $db "insert into foo (string_column) values ('$QQstring_column')"Alternatively, you can use
DoubleApos
orns_dbquotevalue
:
ns_db dml $db "insert into foo (string_column) values ('[DoubleApos $string_column]')"
or
ns_db dml $db "insert into foo (string_column) values ([ns_dbquotevalue $string_colum])"
You will often have to escape special TCL characters. ", {, }, [, and ] are the most common examples.Incorrect:
ns_write "<a href="index.tcl">ACS</a>"Correct:
ns_write "<a href=\"index.tcl\">ACS</a>"
Incorrect:The only exception to this rule occurs with update:
select user_id from users where home_phone = nullCorrect:
select user_id from users where home_phone is null
update users set home_phone = null where user_id = 10
Incorrect.
set num_users [database_to_tcl_string $db "select count(user_id) from users where url = '$foo'"]If $foo is "", num_users will always be 0.
Correct:
set num_users [database_to_tcl_string $db "select count(user_id) from users where (url = '$foo' or ('$foo' is null and url is null))"]
If you wanted to count users that did not visit today:
Incorrect:See Tips for using Oracle for more discussion of this and other Oracle issues.
select count(user_id) from users
where trunc(last_visit) <> trunc(sysdate)(This would not include users that had a
last_visit
of null.)Correct:
select count(user_id) from users
where (trunc(last_visit) <> trunc(sysdate)
or last_visit is null)
to_char
to produce a formatted version of a date column, ordering by this column will order by the alphabetized word, not the sequential date.
Incorrect:
set selection [ns_db select $db "select to_char(posting_time,'Month dd, yyyy') as posting_time from bboard order by posting_time"]Correct:
set selection [ns_db select $db "select to_char(posting_time,'Month dd, yyyy') as pretty_posting_time from bboard order by posting_time"]
if { [database_to_tcl_string $db "select count(id) from foo_table where id=$id"] == 0 } {If there are two hits to this page, perhaps due to a double click, you can have the following sequence:
ns_db dml $db "insert into foo_table (id) values ($id)"
}
In these cases, be sure to use select for update to get the appropriate lock.
For example:
ns_db dml $db "begin transaction"
set selection [ns_db $db 0or1row "select id from foo_table where id=$id for update of foo_table.id"]
if [empty_string_p $selection] {
ns_db dml $db "insert into foo_table (id) values ($id)"
}
ns_db dml $db "end transaction"
To use a database handle in a procedure you should either pass the database handle as a parameter or use the subquery
pool. If you do use the subquery pool
, you must release the handles before the procedure returns to avoid clashes with other procedures.
Incorrect: set db [ns_db gethandle] set selection [ns_db select $db "select posting_date, bboard.* from bboard where sort_key like '$msg_id%' and msg_id <> '$msg_id' order by sort_key"] while {[ns_db getrow $db $selection]} { set_variables_after_queryif $upload_p { set selection [ns_db select $db "select * from bboard_uploaded_files where msg_id='$msg_id'"]
set_variables_after_query ...code... } else { ...code... } } Correct: set db_conns [ns_db gethandle subquery 2] set db [lindex $db_conns 0] set db_sub [lindex $db_conns 1] set selection [ns_db select $db "select posting_date, bboard.* from bboard where sort_key like '$msg_id%' and msg_id <> '$msg_id' order by sort_key"] while {[ns_db getrow $db $selection]} { set_variables_after_query if $upload_p { set sub_selection [ns_db select $db_sub "select * from bboard_uploaded_files where msg_id='$msg_id'"] set_variables_after_subquery ...code... } else { ...code... } }
Common mistake:
if { [info exists foo] and ![empty_string_p $foo] } {
ns_write "$foo"
}
Corrected version:
if { [info exists foo] && ![empty_string_p $foo] } {
ns_write "$foo"
}
ns_return
writes a http contentlength header to the connection. If you break up your output strings and use subsequent calls to ns_write
, you will encounter sporadic cases of a pages that do not finish.
Incorrect:Concatenation may be a more efficient approach for computation and transmission, but using multiple outputs to the connection will produce a streaming effect. Streaming will allow the user to see some data while the rest is being processed and transmitted and may work better for the user in many cases.
ns_return 200 text/html "This is the first form fragment"
...tcl code...
ns_write "This is the second form fragment"
Correct:
append output_string "This is the first form fragment"
...tcl code...
append output_string "This is the second form fragment"
ns_return 200 text/html $output_stringor
ReturnHeaders
ns_write "This is the first form fragment."
...tcl code...
ns_write "This is the second form fragment."
regexp {~second_to_last-([^;]+)} $cookie match second_to_last_visit
set pretty_second_to_last_visit "[ns_fmttime $second_to_last_visit "%m/%d/%y %r"]"
export_var
can be used to protect against undefined variables. For example, export_var foo
will return "" if foo is not defined, or foo's value if foo exists.
Unsafe:
if { $foo == "bar" } {
...tcl...
}Correct: (Note that a exact match with string compare returns a 0.)
if { [string compare $foo "bar"] == 0 } {
...tcl...
}If you just want to see if the variable is empty, use
empty_string_p
if [empty_string_p $foo] {
...tcl...
}
set_variables_after_query
will overwrite any variables that conflict with column names. A common case of this is when you pass user_id as a form or url variable, and then select the user_id column in a table. Using "select *" is particularly dangerous because changes to the database can break existing pages.
set foo "twoIf the user clicks to index.tcl,
lines"
ns_write "< a href=index.tcl?foo=[ns_urlencode $foo]">ACS</a>
ns_conn form
will not contain any data.
This will be fixed in AOLServer 2.3.3.