Serving Secure Files With CFContent Tag's File Attribute In ColdFusion
The other day, when considering which files live inside the wwwroot
folder on the ColdFusion server, I mentioned that "secure files" live outside the wwwroot
folder; but, can be made accessible to the user via ColdFusion. There are many ways to do this; but, perhaps the easiest way is to use the CFContent
tag's file
attribute. This attribute allows any physical file on your server to be sent to the user, regardless of where it lives.
For this demo, imagine that our ColdFusion application has a path mapping to a non-public folder where all of our secure files are stored:
/secure-files
Since these files live outside the wwwroot
folder, the user cannot access them directly. However, when the user provides a correct password, we're going to dynamically serve one of the secure files to the user via the CFContent
tag. Additionally, we're going to use the CFHeader
tag / Content-Disposition
to "change the name" of file as we stream it. This way, we can have a database-driven filename on the server while still providing a user-friendly filename for the download.
<cfscript>
// Setup form value defaults.
param name="form.submitted" type="boolean" default=false;
param name="form.password" type="string" default="";
// If the form has been submitted and the password is correct, send the file to the
// user via the CFContent tag.
if ( form.submitted && ! compare( form.password, "letmein" ) ) {
// The name of our super secret secure file stored in a non-public folder. We're
// going to use the CFContent tag to stream this non-public file to the user.
secureFilename = "0112c97f582d546e39d3547631ef6e85.jpg";
// The user-friendly name of the file as it will appear when downloaded.
clientFilename = "lucy.jpg";
cfheader(
name = "Content-Disposition",
value = "attachment; filename=""#clientFilename#""; filename*=UTF-8''#urlEncodedFormat( clientFilename )#"
);
cfcontent(
type = "image/jpeg",
file = expandPath( "/secure-files/#secureFilename#" )
);
}
</cfscript>
<!--- ------------------------------------------------------------------------------ --->
<!--- ------------------------------------------------------------------------------ --->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>
Please enter password to download secure file
</h1>
<form method="post">
<input type="hidden" name="submitted" value="true" />
<input type="text" name="password" value="" />
<button type="submit">
Download file
</button>
</form>
</body>
</html>
In this case, the CFHeader
tag is actually more complicated than it needs to be for the demo. I have it setup to serve filenames with extended UTF-8 characters, even though my secure file only has simple ASCII characters in it.
But, the CFHeader
tag is only adjusting the filename being presented to the user - the CFContent
tag is doing the real work; its file
attribute points to our non-public /secure-files
directory, where it is dynamically streaming the private file to our user. As such, when the user enters the correct password, they have the following experience:
As you can see, the non-public file is streamed to the user with a new filename.
ASIDE: In this demo we're keeping the secure file in place since it's not user-specific. However, if we were dynamically generating a file on-the-fly and wanted to delete it after ColdFusion was done streaming it, we could include the
deleteFile
attribute on theCFContent
tag.
The file
attribute is only one way for the CFContent
tag to stream data - it also provides the variable
attribute for streaming any binary value to the user. In fact, if you look at my previous post on proxying Gravatar images through ColdFusion for better caching, I use both the file
attribute and the variable
attribute in order server up avatar images from different sources.
The CFContent
tag is just one of those ColdFusion features that makes web application development with CFML really convenient.
Want to use code from this post? Check out the license.
Reader Comments
Great post!
I found out something interesting that I was pulling my hair out for quite some time on: I grabbed some source from another site that did a cfcontent... and cfheader name="Content-disposition". The file downloaded fine, but the filename was always the name of the ColdFusion source file, not the filename specified in the cfheader tag.
I finally realized that the order matters: cfheader should come before cfcontent.
@Russ,
Oh yeah, after the
CFContent
tag is executed the rest of the request is aborted, so anything you want to run must go before that tag. Sorry you ran into that problem, there's no really much to go on - no error or warning or anything like that. Glad you got it figured out!Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →