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 CFUNITED 2010 (Landsdown, VA) with:

Ask Ben: Limit File Upload Size In ColdFusion

By Ben Nadel on

My client has a job inquiry form in which the web user can upload their resume for a job application. These resumes are then emailed to our contact in the human resources department. Since these files are getting attached via email, the client does not want the resumes to be very big. How can I limit the file size of the resumes to be no more than 50 KB?

Putting limitations on a file upload in ColdFusion is a bit tricky. The problems with something like that is that we can't really figure out how large the file is until it is fully uploaded (at least as far as I know - I have never seen a good upload progress bar in ColdFusion). You can do hacks using Javascript to get a File System Object, but those are a huge security risks and will prompt the user for approval. In order to make the processing as transparent as possible, we are going to have to handle it all on the server side.

However, having large files on the server is not usually a problem - it's the emailing of large files that can cause issues (email rejection and inbox size limitations I assume). Therefore, we are not going to try and do anything tricky, just simple server-side size validation.

When it comes to form interfaces and file manipulation of any kind, I like to handle the file validation very last. File manipulation has its own costs that include extra CFTry / CFCatch logic and heavier processing. Therefore, I don't like to do any file validation until I am sure that NOTHING else in the form will cause the form to not be valid. For instance, if someone's email is not valid and the form has to redisplay, I don't want to even bother checking the file as I know that the file cannot be used (since the form must refresh anyway).

Once you do know that you can upload and validate a file, it is very important to use CFTry / CFCatch blocks. There are any number of things that can cause exceptions to be thrown when dealing with an outside system and especially when that system is a file system. The idea is that we never want to show the end user an error if we can recover from it.

Take a look at how I am handling the file size validation in this form. I have tried to recreate a very minimal form that sounds reflective of the one you have built (a resume submission form in ColdFusion):

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!--- Param the form variables. --->
  • <cfparam name="FORM.name" type="string" default="" />
  • <cfparam name="FORM.email" type="string" default="" />
  • <cfparam name="FORM.resume" type="string" default="" />
  • <cfparam name="FORM.submitted" type="string" default="0" />
  •  
  •  
  • <!---
  • With this array we are going to keep
  • track of form validation errors.
  • --->
  • <cfset REQUEST.Errors = ArrayNew( 1 ) />
  •  
  •  
  • <!---
  • Check to see if the form has been submitted.
  • No sense in doing any processing until the
  • form gets submitted. Use the Val() method here
  • because are submitted variable is a string
  • and there is no garuuntee that it will be a
  • numeric value (if someone messed with the form).
  • --->
  • <cfif Val( FORM.submitted )>
  •  
  • <!---
  • Now that the form has been submitted, we want
  • to validate the data before we process it. Of
  • all the validation, checking the file size is
  • going to be the most costly as it requires
  • actually requires file manipulation. Do that
  • last and do that ONLY if no other form errors
  • exist.
  • --->
  • <cfif NOT Len( FORM.name )>
  •  
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "Please enter your name."
  • ) />
  •  
  • </cfif>
  •  
  • <cfif NOT IsValid( "email", FORM.email )>
  •  
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "Please enter a valid email address."
  • ) />
  •  
  • </cfif>
  •  
  • <cfif NOT Len( FORM.resume )>
  •  
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "Please select a file to upload."
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have processed all the non-file parts
  • of the form, let's check to see if there are any
  • errors. If so, then there's not point in processing
  • the file.
  • --->
  • <cfif NOT ArrayLen( REQUEST.Errors )>
  •  
  • <!---
  • Upload the file to the temp directory. Any
  • time you work with file manipulation use a
  • try / catch block as you never know what
  • might go wrong.
  • --->
  • <cftry>
  •  
  • <cffile
  • action="UPLOAD"
  • destination="#ExpandPath( './temp/' )#"
  • filefield="resume"
  • nameconflict="MAKEUNIQUE"
  • />
  •  
  •  
  • <!---
  • Once the file gets uploaded, we should have
  • access to the file properties via the CFFILE
  • object. Let's check the size to see if it's
  • too big. Remember that we know the BYTE size
  • of the file. To make things easier to read,
  • I generally do file size using math. This
  • way I can more easily see what I am dealing
  • with. For this, we are limiting it to 50K
  • which is 50 * 1024 bytes.
  • --->
  • <cfif (CFFILE.FileSize GT (50 * 1024))>
  •  
  • <!--- Add our error message. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "Your resume cannot be more than 50K."
  • ) />
  •  
  •  
  • <!---
  • We have no use for this file. Let's try
  • to delete it from the file system.
  • --->
  • <cftry>
  •  
  • <cffile
  • action="DELETE"
  • file="#ExpandPath( './temp/#CFFILE.ServerFile#' )#"
  • />
  •  
  • <cfcatch>
  • <!---
  • There was an error deleting
  • the file. You can log this,
  • but it's not worth crashing
  • the page over.
  • --->
  • </cfcatch>
  • </cftry>
  •  
  • </cfif>
  •  
  •  
  • <cfcatch>
  •  
  • <!---
  • If there was any error, then alert the
  • user. Also, since this error may have
  • happened after the file upload, let's
  • try to delete the file. Of course, this
  • requires a try/catch if for no other
  • reason, we don't know if the file was
  • uploaded.
  • --->
  •  
  • <!--- Add our error message. --->
  • <cfset ArrayAppend(
  • REQUEST.Errors,
  • "There was a problem uploading your resume."
  • ) />
  •  
  • <!--- Try to delete the file. --->
  • <cftry>
  •  
  • <cffile
  • action="DELETE"
  • file="#ExpandPath( './temp/#CFFILE.ServerFile#' )#"
  • />
  •  
  • <cfcatch>
  • <!---
  • There was an error deleting
  • the file. You can log this,
  • but it's not worth crashing
  • the page over.
  • --->
  • </cfcatch>
  • </cftry>
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we are done validating the form
  • data, we can process the form. Check to
  • see if we have any form errors.
  • --->
  • <cfif NOT ArrayLen( REQUEST.Errors )>
  •  
  • <!---
  • ASSERT: At this point, we know that we do NOT
  • have any form errors. We also know that we
  • have a file ALREADY uploaded whose information
  • is available in the CFFILE variable:
  •  
  • CFFILE.ServerFile (name + ext)
  • CFFILE.ServerFileName
  • CFFILE.ServerFileExt
  • --->
  •  
  • <!--- Process FORM. --->
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Before we render the content, let's clear the
  • content buffer to minimize pre-page white space
  • and tell the browser what kind of content to expect.
  • --->
  • <cfcontent
  • type="text/html"
  • reset="true"
  • />
  •  
  • </cfsilent>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Limit File Upoad Size</title>
  •  
  • <style type="text/css">
  •  
  • body {
  • font: 11px verdana ;
  • }
  •  
  • label {
  • display: block ;
  • margin-bottom: 16px ;
  • }
  •  
  • div.formerrors {
  • color: #990000 ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  •  
  • <cfoutput>
  •  
  • <!---
  • Check to see if we have any form errors to
  • display. We don't really care if the form has
  • been submitted or not since it won't have any
  • errors prior to submission.
  • --->
  • <cfif ArrayLen( REQUEST.Errors )>
  •  
  • <div class="formerrors">
  •  
  • <h4>
  • Please review the following issues:
  • </h4>
  •  
  • <ul>
  • <cfloop
  • index="intError"
  • from="1"
  • to="#ArrayLen( REQUEST.Errors )#"
  • step="1">
  •  
  • <li>
  • #REQUEST.Errors[ intError ]#
  • </li>
  •  
  • </cfloop>
  • </ul>
  •  
  • </div>
  •  
  • </cfif>
  •  
  •  
  • <form
  • action="#CGI.script_name#"
  • method="post"
  • enctype="multipart/form-data">
  •  
  • <!---
  • This hidden form field will alert us that
  • the form has been posted.
  • --->
  • <input
  • type="hidden"
  • name="submitted"
  • value="1"
  • />
  •  
  •  
  • <label>
  • Name:
  • <input
  • type="text"
  • name="name"
  • value="#FORM.name#"
  • size="30" />
  • </label>
  •  
  • <label>
  • Email:
  • <input
  • type="text"
  • name="email"
  • value="#FORM.email#"
  • size="30" />
  • </label>
  •  
  • <label>
  • Resume:
  • <input
  • type="file"
  • name="resume"
  • value=""
  • size="60" />
  • </label>
  •  
  • <input type="submit" value="Upload Resume" />
  •  
  • </form>
  •  
  • </cfoutput>
  •  
  • </body>
  • </html>

Notice that the file handling and manipulation code is fairly verbose. I am not sure if what I am showing you here is the best way to do things, but it works quite well for me. This method should leave minimal garbage on your server as it attempts to delete the uploaded file if it is too big or if an error was thrown. Of course, I have not put in any form processing. It sounds like you have that process under control. Hope that helps.

Tweet This Great article by @BenNadel - Ask Ben: Limit File Upload Size In ColdFusion Thanks my man — you rock the party that rocks the body!



Reader Comments

Just to expand on this? My requirement is upload files only of a specific mime-types, and the most tricky one is .msg (Outlook file). However, since .msg is not a standard file type (not a registered mime type), the server is assigning it with the mime type "application/octet-stream" by default. The problem is that this mime type is the same mime for .dll, .exe, etc..., which should be prohibited. Any take on this?

-Dmitriy

Reply to this Comment

Honestly, I would handle it in the exact same way. I used to use the EXCEPT attribute of the CFFILE, but that never worked out the way I wanted it to since built-in mime-types are not consistent (at best).

You might be able to check the file extension from the form field (don't know off hand):

ListLast( FORM.resume, "." )

But that may be "tmp" for the temp file that gets created for the upload. If that is the case, just upload the file, and check the file extension of the uploaded file:

ListFind( "exe,bat,ini", CFFILE.ServerFileExt )

If it's a bad file extension, delete and report the error. To me, this gives you the most flexability and will have the most consistent outcome.

Now, this of course is geared towards a Windows machine that uses file extensions. If you go to Mac or Linux or something where file extensions are not really used, I have no idea what looks like. No recommendations there. But the above method has worked fine for me over the years.

Reply to this Comment

Ben, it's the ACCEPT attribute, and it would only list accepted mime types. I forgot where I saw it, but I know someone has a mime type structure built with most of the common types already.

Reply to this Comment

Ooops, sorry about the misspelling (shows how often I use it). The problem is that no mime-types are not very consistent. Look at what Dmitriy is saying above - he wants to get a non-standard mime type. Since the server doesn't know this one, he passes through the octet-stream. This is like the catch-all of mime types. I think if you put that in the accept attribute it will actually let anything through - BUT please do not quote me on that as I have not tested it or used it in a long time.

But in my experience, I have even had problems where image files don't have proper mime-types and they get rejected and then we have to open them up in a graphics editor and re-save them.

All in all, checking the mime type just feels less reliable and flexible.

Reply to this Comment

I agree Ben. I had problems in a CMS where CSS files were being uploaded that weren't of mime type text/css, but something else entirely. This was the piece of code I used as a starting point: http://developer.fusium.com/code/ (it's about half-way down) and just added as I needed.

Reply to this Comment

Thanks, however this is not what I need. The system is much more sophisticated than that. First of all, extension check is not acceptable, and second of all, you can get an exe file rename it as a txt; however when you upload it it's still an exe.

Reply to this Comment

Just thinking out of the box, you could also consider using a web based rich text editor. This gives you a little more control on the client side for size limitation and eliminates issues of someone uploading different file formats, macro viruses and such. Some RTE's also supports word paste otpions.

Reply to this Comment

@Dmitriy,

While you can rename an EXE to be TXT, doing so (at least on a window's machine) will not allow it to execute as an EXE when double clicked or invoked from a URL (not sure what CFExecute would do). Plus, there is the issue of "worst case scenario" - if someone did upload an EXE as a TXT, what would happen? Would the EXE ever be executed? What happens to the TXT file? What is the worst that could happen? Is this any different than someone just uploading a corrupted, unusable TXT file?

I guess it becomes a trade-off. Checking the file extension gives you more control over file type acceptance, but does not automatically reject based on mime-type. Doing a mime-type acceptance check might reject falsely.

Perhaps a combo of the two would be best. Check the mime type (after upload); if is a bad mime time (ex. EXE, BAT) then reject. If it is undeterminable (ex. octet-stream), then check the file extension.

Reply to this Comment

Instead of doing <cffile action="upload" ...> then comparing the file size & if it doesnt matches removing it

you can also use
<cfset f = createObject("java","java.io.File").init(form.resume)>
<cfif f.length() lT (50 * 1024)>
<cffile action="upload" destination="location">
</cfif>

The point is that <cffile action="upload"> does nothing but renames the file to the location you specified in the destination attribute.
The file will be uploaded in a temp CF directory before <cffile> comes into play. So one can directly refer the file at its original location to check the size & if it matches can move on & put in the destination location.
Since its a temp location where the file gets uploaded one doesnt need to explcitly delete the file unless you have space limitations.

Reply to this Comment

haven't used this yet myself, but if you the customer is cool with requiring users to have at least Flash 8, cf_flashUpload is supposed to be sweet: http://www.asfusion.com/blog/entry/file-upload-with-coldfusion-flash-forms

http://www.asfusion.com/examples/item/file-upload-with-coldFusion-flash-forms

and then in addition to the file types checking, you'll want to make sure and prevent huge (definition of huge is up to you, but I hear much past 500Mg becomes a problem) files from getting all the way through with the following setting: "Maximum size of post data" - see http://www.coldfusionmuse.com/index.cfm/2006/5/24/Limit.Post

Reply to this Comment

Good point about the max size post. I will have to check out the flash form uploads as well. Thanks for the link.

Reply to this Comment

Unfortunately flash forms aren't really accessible, have poor fallback, and can be a real PITA for users.

A great solution to providing a better user experience, in terms of file uploading, that I've found is SWFUpload.

http://swfupload.mammon.se/

Works really nicely. Falls back to a regular file input if the user doesn't have JS or flash enabled. Lets you write JS handlers to bind to events for actions in the client code. Quite awesome.

Reply to this Comment

Hmm, that looks really slick (swfupload). I just tried the demo and it is very user friendly. I will look into this more thoroughly. Thanks for the link.

Reply to this Comment

I've use cgi.content_length to check the length before the item it uploaded, is there a downside to doing it this way that I am not aware of?

Reply to this Comment

@Mark,

Not sure what CGI.content_length has in it if you have multiple files being posted. The nice thing about going through CFFILE is that you can do content length checks per-file.

Reply to this Comment

Ben is there anyway after the upload has past all the validation and the resume has been uploaded to redirect the user to a different page

Reply to this Comment

@Anthony,

Yeah. After all the form processing is done, just execute a CFLocation tag to send the user on their way to another page.

Reply to this Comment

Hi,

Is it possible to make the upload resume non mandatory? When i try to modify the code it throws up all kinds of errors.
a sample of the code needed would be great !

thanks !

Reply to this Comment

@Liam,

To make it non-mandatory, first, you have to remove the data validation CFIF statement:

<cfif NOT Len( FORM.resume )> .. </cfif>

Then, in the form processing area, rather than just automatically uploading and using the file, you have to check to see if has been uploaded:

<cfif Len( FORM.resume )> ... </cfif>

Reply to this Comment

Hi Ben thanks for the information.
As I am new to Coldfusion, I was wondering if you would explain a few things more for me.

1. do I just cut the code for the <cfif NOT Len( FORM.resume )> down to the end tag and change the <cfif NOT Len( FORM.resume )> to <cfif Len( FORM.resume )> and leave the rest of the code the same?

2. Where in the form processing section would i now place this cut code?

I have tried commenting out the <cfif NOT Len( FORM.resume )> part and it gets to a point where it needs to process the CFFILE.ServerDirectory & "\" & part and because there is no file attached therefore a directory path it errors out.

thanks for your help again !

Liam.

Reply to this Comment

@Liam,

Take out the initial CFIF/CFIF statement for the Len() of the file field. Then, change this CFIF statement:

<!---
Now that we have processed all the non-file parts
of the form, let's check to see if there are any
errors. If so, then there's not point in processing
the file.
--->
<cfif NOT ArrayLen( REQUEST.Errors )>

... to :

<cfif (
(NOT ArrayLen( REQUEST.Errors )) AND
Len( FORM.resume )
)>

This way, you will upload the file if there are no form errors AND the file has been selected.

Reply to this Comment

Hi Ben,
Thought I should my point here, well i was thing why upload a file and then delete, isn't it obivous if we check the file size before uploading like this below:

<cfif VAL(CGI.CONTENT_LENGTH) GT 50000>
<cfset msg = 'File Too Large'>
<cfelse>
Upload Code goes here
</cfif>

Reply to this Comment

@Matt,

I think the file has to be uploaded as part of the request before the CGI scope is even available. Plus, I like doing things per-file since the content_length is the total, not broken up in any way.

Reply to this Comment

@Adrian

That won't work because of the possible race condition. Right after the fileExists() and before the delete the file could vanish, and then the operation would still fail.

Reply to this Comment

Just a thought. There is a nice GNU-Util you can use called "file" that is shipped with linux but also is available for windows.

What it does is check the contents of a file to determine its type.

This means you can identify files from their contents rather than an extension which is easily faked.

so

upload file
check with file util
delete unwanted files
scan if its a valid file with av
make sure to pass back any upload files through your own handler and store them in a folder not accessible via the web (just in-case your web server allows execution)

voila!

Reply to this Comment

@Andrew,

I am not sure how this is different from using ColdFusion to check the file size? Are you saying this happens before the request gets passed to the ColdFusion application?

Reply to this Comment

This is after the upload of the file.

The idea is to do Content Inspection to determine the MIME type of the file and this is something GNU file does really well.

The internal Coldfusion file information stuff should be fine but you mentioned previously that the mime-type stuff was not so great. I find GNU file to be really reliable.

It also has a config file for identifying mime-types.

Reply to this Comment

@Andrew,

Ahh, ok, so its more about the mime type detection than the file size. That makes sense.

Reply to this Comment

Hi Ben

Is there way to check for the file extensions before I actually upload the file. (No Javascript)

Reply to this Comment

I like the post where the the java was used and thought maybe there is one to get a file extension

<cfset f = createObject("java","java.io.File").init(form.name)>

Reply to this Comment

I ma using the java before I upload the file. But I need something to check for extension

Reply to this Comment

@Erik,

Ah sorry, I think I misunderstood what you were saying since you mentioned that you need to do this without Javascript.

By the time you get to ColdFusion, the file has *already* been uploaded to the server. It gets uploaded into a TEMP directory. When you invoke the CFFILE/Upload action, all you are really doing is moving the file form the TEMP directory into the Destination directory.

You might want to check out David Boyer's post on hidden FORM values:

http://misterdai.wordpress.com/2010/06/23/form-scope-hidden-upload-details/

There's a lot you can do behind the scenes.

Reply to this Comment

We might include checking the file size if zero, that will cause the file upload error.
Try saving a text file with no content on it and upload.

Reply to this Comment

@Nelgraine,

Is that true? I am not sure I've actually tried to save a file that is empty. Although, I believe the CFZip tag will error if you try to work with zero-length archives. I'll have to test that out at some point.

Reply to this Comment

I quite like Rahul Narula's way of using Java to view the file size before upload. It definitely beats uploading the whole file(s) and then erroring to the user. Imagine an image upload form that limits users to 50K files and they upload 10 x 5MB files. They would have to wait for 50MB to upload before receiving an error, and that's if the request didn't timeout before that.

Anyone used Rahul's method before? Definitely going to copy that code to file for future use (after testing of course)

Reply to this Comment

Ben,

You can always use SESSION variables to store POST variables. In the advent of an error, re-display form values using your session variables. That way the end user doesn't need to re-type anything anyways.

In PHP, there is a setback though. If you go over the post maximum size which is a lot, it wipes out POST completely. If that happens, no values are returned to the form with your session (the session the you would have assigned from post). But you would have to exceed the file upload size.

Regards,
Ravi.

Reply to this Comment

Ben,

Do you know of any way to override the "Maximum size of post data"
for a particular app or session? We had this set to 50 MB, but we had advertised to our faculty that they could upload videos up to 75 MB.
I asked for our CF administrator to update our "Maximum size of post data" setting to 75 MB. She did, but she would like to only open it up for this app - since the others should never post this much data.

Thanks,
Sue

Reply to this Comment

recently I uploaded two files, the reported file size in windows explorer was 1097 kb for one file and 1853 kb for the other. [2950 kb]

However the cgi.content_length variable was reporting the size as 3019924.

I want to limit total file upload to 10 megabytes so what is the proper cgi.content length to represent this?

Reply to this Comment

I just had a Book from "Brooks" and in his book, "Programming Coldfusion MX 2nd Edition, this is clearly mentioned as:

"A message is written back to the browser letting the user know that the file was too big to upload, and processing of the ColdFusion template is halted before the file can be uploaded. If, however, the value of CGI.content_length is less than or equal to 100,000, the template continues processing uninterrupted."

Acc to him, the file will not be uploaded if the size is gt than matched.

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.