Borrowing some code & ideas from Pete Freitag’s CFIMAGE presentation; this is a rather simple, safe, and easy way to upload photos using a User-Defined Function in ColdFusion. Uploading files is very straight forward in ColdFusion, but I like this function because it takes care of all the little details, so I can concentrate on working with the file afterwards, allowing me to move on to the next iteration.

So, let’s take a quick look at the uploadPhoto function that Pete wrote, which I then modified to add some additional checks and logic. First off, lets assume you have a simple form that looks like this:

1
2
3
4
5
6
7
8
9
 <cfform name="photouploadform" action="#cgi.script_name#" method="post" enctype="multipart/form-data">
  <fieldset>
    <legend>Upload your photo</legend>
      <div class="fm-line fm-req">
                  <label for="photo">Select photograph</label>
                <cfinput type="file" name="photo" value="" required="yes" message="Please select a file to upload"  validateat="onSubmit,onServer" />
      </div>
  </fieldset>
</cfform>

To upload the photo, we simply call the uploadPhoto function:

[-]View Code COLDFUSION
1
<cfset photofile = uploadPhoto('photo',photodir,'jpg,jpeg','overwrite',photoname)>

So, what does the code do? Lets take a look:

[-]View Code COLDFUSION
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
<cffunction name="uploadPhoto" returntype="string" hint="uploads a photo from a form field and returns the filename">
    <cfargument name="filefield" required="true" type="variablename" default="photo" hint="the form field that contains the photo">
    <cfargument name="destination" required="true" default="#expandPath('./')#" hint="specify the destination directory">
    <cfargument name="allowed_extensions" required="false" default="gif,jpg,jpeg,png" type="string" hint="specify valid file extensions">
    <cfargument name="name_conflict" required="false" type="variablename" default="makeunique" hint="specify action for name conflict (makeunique|overwrite|none)">
    <cfargument name="filename" required="false" hint="specify the desired name of the destination file">
    <cfargument name="create_destination" required="false" type="boolean" default="true" hint="specify if destination directory should be created if it does not exist, throw exception otherwise">
 
    <cfset var temp_dir = GetTempDirectory()>
    <cfset var file_name = "">
    <cfset var slash = CreateObject("java", "java.io.File").separator >
 
    <cfif NOT StructKeyExists(form, arguments.filefield)>
        <cfthrow message="filefield is not defined.">
    </cfif>
    <cffile action="upload" filefield="form.#arguments.filefield#" accept="image/jpg,image/jpeg,image/gif,image/png" nameconflict="#arguments.name_conflict#" destination="#temp_dir#">
    <cfif NOT ListFindNoCase(arguments.allowed_extensions, cffile.ClientFileExt) OR NOT ListFindNoCase(arguments.allowed_extensions, cffile.ServerFileExt)>
 
        <!--- this is why we uploaded to a temp dir so they can't execute it before we delete it--->
        <cffile action="delete" file="#cffile.ServerDirectory#/#cffile.ServerFile#">
        <cfthrow message="Sorry file type not allowed.">
    </cfif>
 
    <!--- Make sure directory exists, if not create it if arguments.create_destination is set to true, or throw error --->
    <cfif NOT DirectoryExists(arguments.destination)>
        <!--- Try expanding the path if destination does not start with a slash (UNC Path or *nix path) or drive letter followed by a colon --->
        <cfif NOT (ReFind('[/\\]',Left(arguments.destination,1)) OR Mid(arguments.destination,2,1) eq ':')>
            <cfset arguments.destination = expandPath(arguments.destination)>
        </cfif>
        <cfif NOT DirectoryExists(arguments.destination)>
            <cfif arguments.create_destination>
                <cfdirectory action="create" directory="#arguments.destination#">
            <cfelse>
                <cfthrow message="Destination directory does not exist." detail="#arguments.destination#">
            </cfif>
        </cfif>
    </cfif>
 
    <!--- Ensure that arguments.destination ends with a slash --->
    <cfif NOT ReFind('[/\\]',Right(arguments.destination,1))>
        <cfset arguments.destination &= slash>
    </cfif>
 
    <!--- Make the file name unique prior to moving file --->
    <cfif structKeyExists(arguments,'filename') AND trim(arguments.filename) neq ''>
        <cfset file_name = lcase(arguments.filename)>
    <cfelse>
        <cfset file_name = LCase(cffile.ServerFile)>
    </cfif>
    <cfif LCase(arguments.name_conflict) eq "makeunique">
        <cfset file_name = makeFilenameUnique(arguments.destination&file_name)>
    </cfif>
    <cffile action="move" source="#cffile.ServerDirectory#/#cffile.ServerFile#" destination="#arguments.destination##file_name#">
    <cfreturn file_name>
</cffunction>

The uploadPhoto() function starts by setting some local function variables, followed by checking that the field passed into the filefield argument exists within the ‘form’ scope in ColdFuion. Next, we actually perform the file transfer from the web server to ColdFusion’s temp directory. I am using the function GetTempDirectory() to obtain the directory. According to the documentation:

The directory depends on the account under which ColdFusion is running and other factors. Before using this function in an application, test to determine the directory it returns under your account.

After uploading the file to the temp directory, I check the file extension to ensure that the file is safe to move into under my web root. Remember that it is very easy to fool the mime type that is sent to the server in the request headers when the file is posted, so you need to check the file extension in addition to specify the accept attribute in the <cffile> tag.

Next, I perform some checks on the destination directory. First, I check if the destination directory exists as specified in the arguments. If not, I try to use the expandPath() function in ColdFusion to find the destination directory. Often times I will pass in either the full absolute path to the destination, or may just end up passing in the relative path. This makes it simple to use the function; I don’t need to worry about whether or not to specify the full path.

Lastly, I want to make sure that the file name is unique prior to moving the file from the temp directory to its final location. To do this, I wrote a simple makeFilenameUnique() method:

[-]View Code COLDFUSION
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<cffunction name="makeFilenameUnique" access="public" output="true" returntype="string">
    <cfargument name="path" type="string" required="true" />
    <cfargument name="index" type="numeric" required="false" default="0">
    <cfscript>
        var fullfilename = listlast(arguments.path,"\");
        var pathwithoutfile = replace(arguments.path,fullfilename,"","ALL");
        var fileext = listlast(fullfilename,".");
        var filename = replace(fullfilename,"."&fileext,"","ALL");
        if (arguments.index gt 0){
            arguments.path = pathwithoutfile&filename&arguments.index&'.'&fileext;
        }
        //Writeoutput(arguments.path);
        if (not fileexists(arguments.path)){
         //return arguments.path;
            return fullfilename;
        }else{
            arguments.index++;
            return makeFilenameUnique(pathwithoutfile&filename&'.'&fileext,arguments.index);
        }
    </cfscript>
</cffunction>

This is a simple function that I wrote a while back that creates a unique filename in the same manner that ColdFusion does when using the overwrite=”makeunique” attribute when using some <cffile> actions. This function basically just attempts to append a single numerical digit to the end of the filename. If it is still not a unique file name, it keeps adding 1 to the number; and keeps going until the file name is unique.

Note: these functions use some CF8 features, such as the use of the ++ incremental operator. You can simply remove these and replace it with arguments.index = arguments.index + 1; for example, and this function will work with CF6 or greater.

This entry was posted on Monday, April 14th, 2008 at 10:57 am.
Categories: Adobe, ColdFusion.

One Comment, Comment or Ping

  1. Dan O'Keefe

    Hi Brian,

    I was testing this code in conjunction with playing with the CF8 debugger. The makeFilenameUnique function did not appear to be working, and I found it to be the “return fullfilename” from that function. That file name does not get updated when you increment the index, so it returns the original name and cffile just overwrites the original one that was in conflict. I changed that line to:

    return filename&arguments.index&”.”&fileext;

    That appeared to work for me then.

    Dan

Reply to “Safely upload photos in CF”