Archive

Archive for November, 2008

Using a Captcha with Cold Fusion 8

November 11th, 2008

Anyone who has spent time web surfing in the last few years has seen the popularity of Captchas rise to the point where it’s almost considered bad practice to not use one. The reality is in today’s world you need to protect your application from spam bots and brute force cracks. And fortunately for us Cold Fusion programmers, CF8 makes this incredibly easy with the edition of the cfimage tag.

Although relatively easy from a programmatic standpoint, when using cfimage for captchas you should take your intended audience into consideration in order to make the best decision on setting attribute values. Those you will need to take into consideration are width, height (overall image size), fontsize, and difficulty (how hard the text is to read).
Qusetions you might and should ask before proceeding:

  1. How old are my readers?
  2. How tech-savvy are my readers?
  3. Do my users have any visual disabilities?

I decided for my purposes that my average readers would be in their 20s, technicaly-savvy and unlikely to have any visual impairment. After playing around with the sizes for a while I finally settled on this:

Here is sample output at each difficulty level:

Low:

Medium:

High:

Once you are satisifed with your captchas look you need to do a bit of programming to make it useful. You will need to:

  1. Come up with a method for generating a random string
  2. Store the string for comparison after form submittal
  3. And lastly compare the value entered by the user to our captcha string to make sure it’s a match

[1. Come up with a method for generating a random string]

There are tons of ways you can do this… heck you probably have a method already lying around somewhere already. But incase you don’t, the key thing with random string generators is to chose random numbers from the ascii table and then use chr() to pull out the character. Since my captchas are inherently case sensitive (see part 2), I pull random numbers from 65,90 (A-Z).
*I could have technically used both upper and lowercase since my code converts the input to uppercase prior to doing the hash check anyway, however I felt using one or the other provided more uniformity, and ultimately uppercase letters were easier to read.

function randString(len){
	var i = 0;
	var str = "";

	var chr1 = "";

	for (i = 0; i < len; i++){
		chr1 = chr(randRange(65,90));

		str &=  chr1;
	}
	return str;
}

[2. Store the string for comparison after form submittal]

I chose to store mine within the form begin submitted using the Hash() function… although I could have easily stored this in the session or client scope if I wanted to. You can use any of these methods or come up with another, but consider following my lead b/c it is fundamentally simplier and doesn’t require an active memory store.
*Take note that hashing the string means the input is case sensitive.

<input name="rs" type="hidden" value="<cfoutput>#hash(rs)#</cfoutput>" />

[3. And lastly compare the value entered by the user to our captcha string to make sure it’s a match]

In this case we have to hash the input (form.captcha) and compare it to our stored value (form.rs):

<cfset rs = hash(ucase(form.captcha))> <!--- hash input --->

<cfif rs eq form.rs and len(trim(msg))> <!--- do comparison --->

For detailed information on cfimage, check out the livedocs entry from adobe.

Download the complete example.

web development

Integrate virus scanning for user submitted files.

November 9th, 2008

No matter what type of application you are building if your users are going to be uploading files it is to your advantage (and that of your users) that you integrate real time virus checking. Before I proceed remember this:

  1. Virus checking is free/low cost  (depending upon the product and wether you are an individual or business)
  2. It is relatively easy to build into your applications

Looking around the web you will find various virus checking applications, some free, some not. Some things to consider when selecting antivirus software will be: 

  • Does the program support a CLI (execution from the command line)
  • How reliable the software is
  • How easily manageable the virii definitions are
     
I took some time to research various programs and ultimately chose AVG. I have used this program for years with excellent results, the definitions work on scheduled updates (no maintenance), and it has never once crashed my system. Sounds GOOD, so lets get started:
 
  1. You need to download AVG Free. *For test purposes we will be working with the free edition.
  2. Install the software! (I hope this was obvious to you.)
  3. Create a batch file that executes avgscan.exe with the parameters you want to use
  4. Integrate this batch file into your cold fusion program
     
I’ll skip screen shots for downloading and installing AVG for brevity.
 
[3 Create a batch file that executes avgscan.exe with the parameters you want to use] 
Although there are quite a lot of command line options availble to us, only a few are really necessary. I decided on these 4.
 
  1. /HEUR           Heuristic Analysis /path,path/
  2. /EXT              test these extensions /for example EXT=*/
  3. /ARC             test archives
  4. /REPORT        Report to file /file name/
     
*note that the /report option is used so that we can parse out the results of the scan
 
Here is a look at the completed batch file (vscan.bat):
@echo off
cd\program files\grisoft\avg free\
avgscan.exe %1 /heur /ext=* /arc /report c:\%2.txt
cd\
type %2.txt
rem del %2.txt

You can decide on your own set of options by reviewing the avgscan /? command as shown below:

C:\Program Files\Grisoft\AVG Free>avgscan /?
AVG7 Anti-Virus command line scanner
Copyright (c) 2006 GRISOFT, s.r.o.
/SCAN        Scan test /path,path/
/HEUR        Heuristic Analysis /path,path/
/EXCLUDE     Exclude path or files from scan
/@           Command file /file name/|
/EX          test these extensions /for example EXT=*/
/NOEXT       do not test these extensions /for example NOEXT=JPG/
/SMART       Smart scan
/ARC         test archives
/RT          test run-time compressions
/CLEAN       clean automatically
/TRASH       Move infected files to the Virus vault
/QT          Quick Test
/LOG         Generate a test result file
/MACROW      report macros
/PWDW        Report password-protected files
/IGNLOCKED   Ignore locked files
/REPORT      Report to file /file name/
/REPAPPEND   Append to the report file
/REPOK       report uninfected files as OK
/STOPLEVEL   Pause on detection /1-n/
/NOBREAK     Do not allow CTRL-BREAK to abort
/NOBOOT      Skip MBR/BOOT check
/NOMEM       Do not test memory
/MEM         Scan active processes
/NOHIMEM     Do not test upper memory
/NOSELF      Do not self-check AVG
/SKIPRP      Skip reparse points (NTFS only)
/SPY         Use Anti-Spyware Scanner
/SMS         Generate report in Management Information File (MIF) format
/?           Display help on this topic
/HELP        Display help on this topic
/DELAY       Cooperative mode (sleep during scanning)

 

[4 Integrate this batch file into your cold fusion program]

Obviously we need to have CF call our batch file (vscan.bat). CFEXECUTE to the rescue:

<cfexecute
    name="c:\documentready\_bin\vscan.bat"
    arguments="#tmpFile# #session.user_key#"
    variable="getOutput" timeout="15"></cfexecute>

Thanks to our /report switch and the Type from our batch file the results of our virus scan is dumped to the screen allowing us to parse the results returned in getOutput. Pretty neat eh?

To help you get an idea of what you will be parsing, and what information is available, here is a sample output (take note that the file input parameter must be a full path, and the output you are viewing here is for a file that is not infected with a virus):

C:\DocumentReady\_bin>vscan.bat c:\foo.txt foo
AVG7 Anti-Virus command line scanner
Copyright (c) 2006 GRISOFT, s.r.o.
Program version 7.5.549, engine 442
Virus Database: Version 270.9.0/1771  2008-11-06
Tested: 1 files, 2 sectors
Infections: 0
Errors: 0
AVG  7.5
Copyright (c) GRISOFT,s.r.o. 2006
Program version 7.5.438  Engine: 442 database version 270.9.0/1771
Command line: [c:\foo.txt /heur /ext=* /arc /report c:\foo.txt]
------------------------------------------------------------
Test start 11/6/2008 12:10:15
Elapsed time 4 sec.
------------------------------------------------------------
Scanned         files      :    1
Scanned         sectors    :    2
No viruses found.
------------------------------------------------------------

To do our parsing we will be using Regular Expressions. If you aren’t famlair with regular expressions, they are essentially the hottest tool you can use to parse through text and although they appear duanting at first, with practice they become both second nature and essential to your programming needs.

<cftry>
	<cfexecute
            name="c:\documentready\_bin\vscan.bat"
            arguments="#tmpFile# #session.user_key#"
            variable="getOutput"
            timeout="15"></cfexecute>
<cfcatch>
      <cfset void = showError("Upload failed, virus check timed out.")>
</cfcatch>
</cftry>
<cfset infectedFiles = "Infected        files      :    (\d+?)?">
<cfset pos = REFindNoCase(infectedFiles,getOutput,1,true)>
<cfif ArrayLen(pos.pos) gte 2>
       <cfset infectedCount = mid(getOutput,pos.pos[2],pos.len[2])>
<cfelse>
       <cfset infectedCount = 0>
</cfif>
<cfif infectedCount>
       <cfset virusname = ListGetAt(getOutput,5,chr(10)&chr(13))>
       <cfset virusname = ReplaceNoCase(virusname,"""#tmpFile#"" ...
                                        Virus identified ","","ONE")>
       <cffile action="delete" file="#tmpFile#">

       <cfset void = showError("Virus Found, " & virusname)>
</cfif>

For more information on Regular Expressions check out the Introduction to Regular Expressions (Scripting) on MSDN.

Uncategorized

Use cftimer to help optimize specific portions of code.

November 9th, 2008

Why?

cftimer provides a simple and easy way to determine how long your code is taking to execute. Just assign it a label and turn on debugging output. The results will be displayed under the category CFTimer Times in the debug output measured in milliseconds. Ex. “[62ms] MyCodeBlock”

How?

<cftimer label="MyCodeBlock"> ... {some code} ... </cftimer>

Note:

Make sure you have Timer Information turned on in the Debug Output Settings from within the Coldfusion Administrator. If you still aren’t seeing any debug output make sure that debugging is enabled and that your ip has been added to the Debugging IP Addresses list.

Quick Tips

Make sure your SQL Statements are using cached execution plans.

November 9th, 2008

Why?

When a query is passed to Microsoft SQL Server, a number of things have to happen in order for you to get a result set back. First, before anything can be done your SQL Statement is compiled by the Query Optimizer which creates an Execution Plan based on statistical data and things like indexes. Essentially it is making its best attempt to create a procedure that is as highly optimized as possible and can return the results your’re looking for.

Once this Execution Plan is created it is then cached on the server so that the next time a query with the same parameters and structure is called it can simply run that existing plan instead of having to build a new one which can be, and is often, processor and i/o intensive; creating undo overhead.

Using cached execution plans is considered a best-practice because it allows your queries to execute much faster (since the plan doesnt have to be constantly rebuilt/compiled) and as a side effect because you are passing data to typed values (int, varchar, etc.) you avoid any possibilities of SQL Injection Attacks.

How?

  • used stored procedures
  • use cfqueryparam for all values in your queries (even constants)
     

Note:

This is applicable to all languages and application types: web, desktop, cli… wether it be in cfml, c#, or c++, SQL essentially functions the same way with the exception of the driver being utilized. If you aren’t sure your execution plans are being cached take the time to look up the correct way to do it. You will be glad you did.

Quick Tips

Convert datetimes to integers when doing date-only range searches in SQL.

November 9th, 2008

From comment: 12/22/09

I discovered later on, this is actually a very BAD implementation. The reason is when using indexes, executing a function on each item requires an entire index scan to be done instead of an index seek. This completely screws up doing a range search for dates on a clustered index. Yikes.

The methods above work, but boy or they ever inefficient. Avoid them if at all possible. Consider using things like DateDiff(), etc.

And Remember, when your writing high performance queries always study the execution plan and reduce bottlenecks!

Why?

Sooner or later you will need to do date-only range searches (where you are not concerned with times). Many programmers will actually specify a start time of 12:00 AM and ending time of 11:59:59 PM to get around this. But if you write a lot of queries you will quickly find this to be an annoyance and without an appropriate solution it can be easy to forget about times all together; creating a hard to find bug that will crop up and have your users up in arms.

How?

Since datetimes are stored as binary values in SQL we can utilize this to our advantage by casting the datetime object to a decimal and passing that result to the floor() function to isolate the date and effectively chop off the time.

declare @startdate datetime
declare @enddate datetime

set @startdate = '01/01/2008'
set @enddate = '03/01/2008'

select
	fname + ' ' + lname as emp_name,
	convert(varchar(10), hire_date, 101) as hire_date,
	hire_date as og_hire_date,
	floor(cast(hire_date as decimal(9,4))) as og_hire_date_cast

from employees

where
     floor(cast(hire_date as decimal(9,4))) between @startdate and @enddate

Note:

Conversely if we only wanted the time, we are concerned with what is on the right side of the decimal. Using the function makes this a cinch. For more information on how SQL stores datetimes internally, check out Date and Time (Transact-SQL) on MSDN

SQL Function, MSDN Ref.
CONVERT ( data_type [ ( length ) ] , expression [ , style ] )
CAST ( expression AS data_type [ (length ) ])
FLOOR ( numeric_expression )

Quick Tips

Format your datetime values directly in your SQL query.

November 8th, 2008

Why?

Formatting datetimes directly in your query is significantly faster than using dateFormat() and timeFormat() in ColdFusion (and most other languages) and simplifies your query output by having formatting already completed.

How?

Formatting datetimes is incredibly simple. All you need do is call Convert() and specify the format parameter you wish to use. 

SELECT top 1
     convert(varchar(10), birthdate, 101) AS birthdate
FROM employees

Notice the style parameter of 101. This returns the date in US Standard format mm/dd/yyyy. A plethora of style parameters are available including options for times and standard formats of countries outside the US. Check MSDN for a complete listing and more information on the Convert method.

SQL Function, MSDN Ref.
     CONVERT ( data_type [ ( length ) ] , expression [ , style ] )

Quick Tips