Download file to browser using CFScript

Categories: Development
Tags:
Comments: 3 Comments
Published on: October 20, 2010

I was working on a page that listed out previously generated exports and I wanted to provide a link that someone could click and download the actual CSV that was generated. I have done this in the past with tag based CF using something along the lines of this:

ColdFusion:
  1. <cfoutput>
  2.     <cfheader name="Content-Disposition" value="attachment; filename=#expandPath(relativePath)#">
  3.         <cfcontent type="application/unknown" file="#expandPath(relativePath)#" deletefile="No" reset="true" />
  4.     </cfheader>
  5. </cfoutput>

Well as I am now writing most of my components in CFScript, I wanted to accomplish the same thing, but without having a dummy view or a tag based component to pull it off. After pulling my hair out for a while, I was able to piece this code together based on parts of several tips I found on other blog posts and it worked like a charm:

ColdFusion:
  1. <cfscript>
  2.     private void function serveFile(string filePath){
  3.         var fileContent = fileRead(expandPath(filePath));   
  4.        
  5.         var context = getPageContext();
  6.         context.setFlushOutput(false)
  7.        
  8.         var response = context.getResponse().getResponse();
  9.         response.reset();
  10.         response.setContentType("text/csv");   
  11.         response.setContentLength(len(fileContent));       
  12.         response.setHeader("Content-Disposition","attachment; filename=#listLast(filePath,'\')#");
  13.        
  14.         var out = response.getOutputStream();     
  15.         out.write(ToBinary(ToBase64(fileContent)));       
  16.         out.flush();       
  17.         out.close();
  18.     }
  19. </cfscript>

Granted my scenario is specifically catered to downloading a CSV, but I believe just changing the contentType to something applicable to your situation would work just fine. Hope this helps someone!

UPDATE

I have since wanted to move this to a central location and use it for any file downloads. At first I changed the mime type to "application/unknown" and that was fine. Later I found I could find most mime types automatically using the library coldfusion.util.MimeTypeUtils and also was told to scrub spaces and invalid characters out of the attachment name by a coworker, so here is the modified code.

ColdFusion:
  1. <cfscript>
  2.     variables.mimeTypeUtils = createObject("java","coldfusion.util.MimeTypeUtils");
  3.    
  4.     public void function download(required string filePath){
  5.         var fileContent = fileRead(expandPath(filePath));
  6.         var context = getPageContext();
  7.         context.setFlushOutput(false);
  8.         var response = context.getResponse().getResponse();
  9.         response.reset();
  10.         response.setContentType(getMimeType(arguments.filePath));
  11.         response.setContentLength(len(fileContent));
  12.         response.setHeader("Content-Disposition","attachment; filename=#scrubFileName(listLast(filePath,'\'))#");
  13.         var out = response.getOutputStream();
  14.         out.write(ToBinary(ToBase64(fileContent)));
  15.         out.flush();
  16.         out.close();
  17.     }
  18.    
  19.     private string function getMimeType(required string file){
  20.         var mimeType = variables.mimeTypeUtils.guessMimeType(arguments.file);
  21.         if(isDefined("mimeType")){
  22.             return mimeType;
  23.         }
  24.         else{
  25.             return "application/unknown";
  26.         }
  27.     }
  28.    
  29.     private string function scrubFileName(required string fileName){
  30.         var extension = reverse(listfirst(reverse(arguments.fileName),"."));
  31.         arguments.fileName = reverse(listrest(reverse(arguments.fileName),"."));
  32.         arguments.fileName = Replace(arguments.fileName, ' ', '_', 'all');
  33.         arguments.fileName = REReplace(arguments.fileName, '\W', '', 'all');
  34.         return arguments.fileName & "." & extension;
  35.     }
  36. </cfscript>

3 Comments - Leave a comment
  1. Tony Nelson says:

    What an improvement!

    I'd rather create a tiny tag-based component than have to work directly with the underlying page context.

    Interesting approach though.

  2. scott says:

    I know exactly what you mean. It was one of those situations where I basically got stubborn and said I want to do it this way and refuse to give up.

    I did decide at one point that I had one hour to figure it out, or I had to go the other route. Luckily I figured it out 5 minutes later!

  3. scott says:

    Updated the post with a more polished version of what I showed before.

Leave a comment

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>