Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandy Clark
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Sandy Clark

Getting The MetaData For A File Upload In ColdFusion

By
Published in Comments (7)

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!

Want to use code from this post? Check out the license.

Reader Comments

1 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.)

15,841 Comments

@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.

36 Comments

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

15,841 Comments

@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!

2 Comments

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.

1 Comments

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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel