Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with:

Getting The MetaData For A File Upload In ColdFusion

By Ben Nadel on
Tags: ColdFusion

Yesterday, I discovered that you could call CFFile-Upload twice on the same file in a single ColdFusion request. This turns out to be an awesome feature for implementing secure upload validation. This morning, I wanted to take that idea and simply factor it out into its own user defined function (UDF) - getFileUploadMetaData(). This UDF would take the form field of the given file and return a subset of the metadata that would usually be returned a the result of a standard CFFile-Upload action. This way, you could use the metadata to help validate the file upload.


 
 
 

 
  
 
 
 

The code below is basically the same as it was yesterday; however, instead of calling the CFFile-Upload action explicitly for validation, I'm replacing it with a call to getFileUploadMetaData(). Then, I am validating both the file extension and the content type of the post.

  • <cffunction
  • name="getFileUploadMetaData"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="I return the meta data of the file upload contained in the given form field.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="fileField"
  • type="string"
  • required="true"
  • hint="I am the form field that contains the given file."
  • />
  •  
  • <!--- Define the local scope (testing on CF8). --->
  • <cfset var local = {} />
  •  
  • <!---
  • Get the system's temp directory. We'll be "uploading" the
  • file here in order to query for its properties.
  • --->
  • <cfset local.tempDirectory = getTempDirectory() />
  •  
  • <!--- Upload the file to the temp directory. --->
  • <cffile
  • result="local.upload"
  • action="upload"
  • filefield="#arguments.fileField#"
  • destination="#local.tempDirectory#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Since we don't need the physical file anymore, let's delete
  • it immediately.
  • --->
  • <cfset fileDelete( "#local.tempDirectory##local.upload.serverFile#" ) />
  •  
  • <!---
  • Create the meta data for the file. Since the temp directory
  • doesn't afford proper naming, and we don't know how the user
  • is going to save the file, we can really only capture the
  • client-name in a meaningful way.
  • --->
  • <cfset local.metaData = {
  • clientFile = local.upload.clientFile,
  • clientFileExt = local.upload.clientFileExt,
  • clientFileName = local.upload.clientFileName,
  • contentSubType = local.upload.contentSubType,
  • contentType = local.upload.contentType,
  • fileSize = local.upload.fileSize
  • } />
  •  
  • <!--- Return the meta data. --->
  • <cfreturn local.metaData />
  •  
  • </cffunction>
  •  
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  •  
  • <!--- Param the form fields for the submit. --->
  • <cfparam name="form.submitted" type="boolean" default="false" />
  • <cfparam name="form.file" type="string" default="" />
  •  
  • <!--- I will hold errors generated during the upload processing. --->
  • <cfset errorMessage = "" />
  •  
  • <!--- I will hold the name of the uploaded file (if successful). --->
  • <cfset imagePath = "" />
  •  
  •  
  • <!--- Set the path for the uploads folder. --->
  • <cfset uploadDirectory = expandPath( "./uploads/" ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Check to see if the form has been submitted. --->
  • <cfif form.submitted>
  •  
  •  
  • <cftry>
  •  
  • <!--- Make sure a file was selected. --->
  • <cfif !len( form.file )>
  •  
  • <cfthrow
  • type="FileNotFound"
  • message="Please select a file to upload."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- Get the meta data for the uploaded file. --->
  • <cfset metaData = getFileUploadMetaData( "file" ) />
  •  
  • <!---
  • Check to see if we should continue processing. If the
  • file is not an image, there's nothing more to do. In this
  • case, we are checking the reported file type and content
  • type of the post.
  • --->
  • <cfif (
  • !reFind( "(?i)jpe?g|png|gif", metaData.clientFileExt ) ||
  • (metaData.contentType neq "image")
  • )>
  •  
  • <!--- ALERT: Possible Attack!! --->
  • <cfthrow
  • type="InvalidImageFile"
  • message="Please select a valid image file."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • If we've made it this far, then we know the user has
  • selected a valid image file. Now, we want to move the
  • file into the uploads directory. Let's use the CFFile
  • tag AGAIN in order to let ColdFusion handle the file
  • conflict resolution.
  • --->
  • <cffile
  • result="upload"
  • action="upload"
  • filefield="file"
  • destination="#uploadDirectory#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Set the path of the image so we can display it back
  • to the user.
  • --->
  • <cfset imagePath = "./uploads/#upload.serverFile#" />
  •  
  •  
  • <!--- Catch any upload processing errors. --->
  • <cfcatch>
  •  
  • <!--- Set the error message for the user. --->
  • <cfset errorMessage = cfcatch.message />
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Reset the output buffer. --->
  • <cfcontent type="text/html; charset=utf-8" />
  •  
  • <cfoutput>
  •  
  • <!doctype html>
  • <html>
  • <head>
  • <title></title>
  • </head>
  • <body>
  •  
  • <h1>
  • Upload An Image
  • </h1>
  •  
  •  
  • <!--- Check to see if we have an error message to display. --->
  • <cfif len( errorMessage )>
  •  
  • <p>
  • <strong>Ooops:</strong> <em>#errorMessage#</em>
  • </p>
  •  
  • </cfif>
  •  
  •  
  • <form
  • method="post"
  • action="#cgi.script_name#"
  • enctype="multipart/form-data">
  •  
  • <input type="hidden" name="submitted" value="true" />
  •  
  • <p>
  • Please select an Image to upload:<br />
  • </p>
  •  
  • <p>
  • <input type="file" name="file" size="30" />
  • <input type="submit" value="Upload Image" />
  • </p>
  •  
  • </form>
  •  
  •  
  • <!---
  • Check to see if we have an uploaded image to display
  • back to the user.
  • --->
  • <cfif len( imagePath )>
  •  
  • <h3>
  • Your Upload:
  • </h3>
  •  
  • <p>
  • <img src="#imagePath#" width="400" />
  • </p>
  •  
  • </cfif>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

In my last post, we were checking the validity of the file using ColdFusion's isImageFile() function. While it might sound like that is a better approach, the isImageFile() function isn't doing anything more than looking at the file's extension and ensuring sure that it is compatible with the type of file extensions that ColdFusion can typically read. As such, our explicit file extension check is, perhaps more limited in scope, but the same in approach.

NOTE: This is based on the ColdFusion 8 documentation. I am not sure, offhand, if the implementation of isImageFile() has changed in recent releases.

There's not really any new information in this post - simply a different take on the same problem and solution from yesterday. Still, I love the fact that this can be done in ColdFusion - it's super useful!




Reader Comments

Both this and your previous tutorial are great tips that I'm definitely going to be implementing into my code.

Thank you for creating these quick insightful videos!

(Also, what IDE are you using in you videos? Looks very nice.)

Reply to this Comment

@Andrew,

Thanks a lot! I'm pretty excited about this new approach. Makes file handling a lot easier, in my opinion.

The IDE is Sublime Text 2. I recently started trying it out a few weeks ago. It's pretty fast and never seems to crash.

Reply to this Comment

Great way of wrapping it up into a function Ben. Thought I'd share what I mentioned on Twitter that you can also pull that information out from where ColdFusion hides it.

http://misterdai.yougeezer.co.uk/2010/06/23/form-scope-hidden-upload-details/

You're method is would definitely be more future proof than mine, as it doesn't have to worry about changes to undocumented features :)

Still think it's a shame Adobe didn't get something like this into CF10. I did receive an email stating it had been verified and targeted for the Beta version, but it never made it and has since been closed with "NotEnoughTime".

https://bugbase.adobe.com/index.cfm?event=bug&id=3041712

Reply to this Comment

@David,

Yeah, there's definitely a lot of really good stuff behind the scenes in *so* many of the ColdFusion objects. I would love for them to start exposing more of this stuff. It seems like little things make it through with each iteration; but, more would be cool!

Reply to this Comment

That's amazing that you can still read properties of the client-side file from cffile after deleting the server file that you just uploaded. Honestly though I'm really surprised Adobe hasn't taken it a step further and allow us to read the meta-data BEFORE uploading. Or can we?

Should I be concerned that a user could upload a file with "../" in the filename in order to break out of my temp directory? I intend to test this myself, but I figured I'd ask since I was already posting a comment.

Reply to this Comment

Hey Ben, thanks for the info!

Just thought I'd mention there's a potential issue here where a person might (although not very likely) upload a read-only file and then it's possible the file deletion could fail on the read-only file. I know I've seen file deletes fail on read-only files, although I don't know if that happens in the system's temp directory. But just in case I would add mode="775" and attributes="temporary" to the cffile tag. In theory that should reset the uploaded file's properties to ensure that it's deletable.

@Matt I'm pretty sure the server is getting a fully-qualified file path, so if there were any instances of ../ in the supplied path, that would applied on the client side and then when moving the file on the server side, the server would just peel away the file name at the end and ignore the rest of the path (except for your info), so even if any ../ instances survived the transition from the client machine to the http request, they won't be part of that filename at the end of the supplied path.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.