I've built an online system that allows users to download PDF files using ColdFusion. Users have to log in before they can download the files (PDF & Microsoft Office documents). (This application is only for our company staff.)
However, only today I found out that anyone with internet access can view the files. With only certain keywords such as 'Medical Form myCompanyName' in a Google search, they can view the PDF files using the browser.
How can I prevent this?
UPDATE
this is what my problem is. i've created a folder for all of the PDFs file. each of the files is called using ID from database. if let's say a user wanted to view Medical Form, the link would be: http://myApplication.myCompanyName/forms.cfm?Department=Account&filesID=001.
if the user copy this url & log out from system, he/she will not be able to view this file.(login page will be displayed)
However, without the url, other internet users sstill can view the pdf files just by search it on the net, and the search engine will gives a link that direct it to the folder itself, without having to login.
Example:
Medical Form's pdf file is stored in a folder named Document. when an internet user search for Medical Form, the search engine will link it to: http://myApplication.myCompanyName/Document/Medical%20Form.pdf
we have lots of PDF files in this folder and most of it are confidential, and for internal view purpose only. in php, we can disable this by using .htaccess. i'd like to know if there's anything like this for coldfusion?
You can send files through the code with single line like this:
<cfif isAuthorized>
<cfcontent file="/path/to/files/outside/of/web/root/Form.pdf" type="application/pdf" reset="true" />
</cfif>
ColdFusion FTW, right.
Please note that handling large files (say, 100MB+) may cause some problems, because files being pushed to RAM before sending. Looks like this is not correct any more, as Mike's answer explains.
Another option is to use content type like x-application
if you want to force download.
UPD
You want to put this code into the file (let's say file.cfm) and use it for PDF links. Something like this:
<a href="file.cfm?filename=Xyz.pdf">Download file Xyz.pdf</a>
file.cfm:
<!--- with trailing slash --->
<cfset basePath = "/path/to/files/outside/of/web/root/" />
<cfif isAuthorized AND StructKeyExists(url, "filename")
AND FileExists(basePath & url.filename)
AND isFile(basePath & url.filename)
AND GetDirectoryFromPath(basePath & url.filename) EQ basePath>
<cfcontent file="#basePath##url.filename#" type="application/pdf" reset="true" />
<cfelse>
<cfoutput>File not found, or you are not authorized to see it</cfoutput>
</cfif>
UPD2
Added GetDirectoryFromPath(basePath & url.filename) EQ basePath
as easy and quick protection from the security issue mentioned.
Personally I usually use ID/database approach, though this answer was initially intended as simple guidance, not really compehensive solution.
You need to store your PDF's outside of your web realm.
So lets say the base of your web app is
/website/www
All http (web) requests are served from there.
/website/pdf
could be a path where all PDF's are stored. This path isn't accessible via URL as its not served by your web server.
Then in www
you have something like
downloadpdf.cfm?file=NameOfPDF.pdf
Which does your checks to ensure its an appropiate user and if so serves the document
<cfcontent type="application/pdf" file="/website/pdf/#url.file#" />
Using cfcontent, pre cf8, is a really bad idea, as it loads the entire file into memory before transmission. CF8 and later will actually stream from disk, which resolves the memory issue. However if you have large files, users on slow connections, and/or heavy downloads you still have to worry about thread starvation. Each download with cfcontent ties up a thread for the duration of the download.
Depending on your web server you might be able to route around this by using an x-sendfile extension. This allows you to send an http header with the path to a file outside of your web root, and have your web server handle sending the file, freeing up cf to do further work.
Here's an article by Ben Nadel about using mod_xsendfile on apache, http://www.bennadel.com/blog/2170-Streaming-Secure-Files-Efficiently-With-ColdFusion-And-MOD-XSendFile.htm and here's an equivalent IIS7 XSendFile plugin https://github.com/stakach/IIS-X-Sendfile-plugin
You might checkout the snippet of code for CFWheels SendFile() helper tag http://cfwheels.org/docs/1-1/function/sendfile
https://gist.github.com/1528113