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):
Launch code in new window » Download code as text file »
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.
Download Code Snippet ZIP File
Comments (28) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
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
Posted by Dmitriy on May 2, 2007 at 9:13 AM
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.
Posted by Ben Nadel on May 2, 2007 at 9:19 AM
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.
Posted by Phil Duba on May 2, 2007 at 9:39 AM
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.
Posted by Ben Nadel on May 2, 2007 at 9:47 AM
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.
Posted by Phil Duba on May 2, 2007 at 9:50 AM
That's a cool struct to have lying around. I like the ext-lookup. Nice.
Posted by Ben Nadel on May 2, 2007 at 9:59 AM
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.
Posted by Dmitriy on May 2, 2007 at 10:15 AM
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.
Posted by Christopher Wigginton on May 2, 2007 at 10:15 AM
@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.
Posted by Ben Nadel on May 2, 2007 at 10:30 AM
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.
Posted by Rahul Narula on May 2, 2007 at 1:18 PM
Oooooh, nice tip :) I will have to give that a try.
Posted by Ben Nadel on May 2, 2007 at 1:28 PM
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
Posted by Aaron Longnion on May 2, 2007 at 2:30 PM
Good point about the max size post. I will have to check out the flash form uploads as well. Thanks for the link.
Posted by Ben Nadel on May 2, 2007 at 3:48 PM
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.
Posted by Elliott Sprehn on May 4, 2007 at 2:29 AM
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.
Posted by Ben Nadel on May 4, 2007 at 7:00 AM
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?
Posted by Mark B on Aug 31, 2007 at 3:45 PM
@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.
Posted by Ben Nadel on Aug 31, 2007 at 3:47 PM
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
Posted by Anthony on Oct 19, 2007 at 5:49 AM
@Anthony,
Yeah. After all the form processing is done, just execute a CFLocation tag to send the user on their way to another page.
Posted by Ben Nadel on Oct 19, 2007 at 7:07 AM
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 !
Posted by liam on Mar 11, 2008 at 2:22 AM
@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>
Posted by Ben Nadel on Mar 11, 2008 at 7:22 AM
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.
Posted by liam on Mar 11, 2008 at 10:25 PM
@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.
Posted by Ben Nadel on Mar 12, 2008 at 7:19 AM
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>
Posted by Matt on Sep 19, 2008 at 4:14 AM
@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.
Posted by Ben Nadel on Sep 19, 2008 at 8:10 AM
You could dispense with the cftry/cfcatch around the file deletion by using FileExists().
Posted by Adrian Lynch on Nov 12, 2008 at 12:25 PM
@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.
Posted by Elliott Sprehn on Nov 12, 2008 at 1:21 PM
Really? Why would the file disappear after a cffile action="upload"?
Posted by Adrian Lynch on Nov 12, 2008 at 3:41 PM