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 Scotch On The Rocks (SOTR) 2011 (Edinburgh) with:

Learning ColdFusion 8: CFZip Part I - Zipping Files And Directories With CFZip

By Ben Nadel on
Tags: ColdFusion

Before ColdFusion 8, if we needed to create or extract ZIP archives in ColdFusion, we had to mess around with the Java zip libraries. As you can see from things like my ZipUtility.cfc ColdFusion component, the Java zip libraries can give us a lot of power, but they are not the easiest things to work with. ColdFusion 8 has really made it a priority to make the work of developers easier and the new CFZip and CFZipParam tags really demonstrate that mission nicely. The ColdFusion 8 CFZip tag allows us to:

  • Create zip archives
  • Extract zip archives
  • List the contents of zip archives
  • Delete zip archives
  • And, do just about all of the above in a piece-wise fashion

While this list is not long, it is a lot of information to cover. Therefore, I am going to break up my ColdFusion 8 CFZip tutorial into several parts, each of which will cover one of the four main actions (create, extract, list, delete) listed above. To start with, let's take a look at creating zip archives in ColdFusion 8. As it turns out, creating zip archives is also a lot of information to cover; therefore, we are actually going to break this tutorial up into two parts: zipping files and directories with the ColdFusion 8 CFZip tag alone and a second part that will utilize both the CFZip and CFZipParam tags.

Now, just about the smallest action we can take is the zipping of a single file:

  • <!---
  • To start with, we are zipping up a single file. This
  • will result in a zip archive that contains one file
  • in the root directory.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/smile.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • />

This is going to create the zip archive file, images.zip. By default, ColdFusion will attempt to modify an existing zip archive file at the given path rather than overwriting it. Therefore, if images.zip already exists, ColdFusion will attempt to add the file, "smile.jpg" to this existing archive. But of course, if this archive doesn't already exist, ColdFusion will create it.

Then, assuming we have the existing zip from above, if we tried to run this code, adding a different image to the same archive:

  • <!---
  • Zip image file. If the zip archive already exists,
  • we are going to add the image to that archive. If
  • the archive does not yet exists, we will create a
  • new zip archive.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/funny.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • />

... we will end up with a zip directory structure that looks like this:

./funny.jpg
./smile.jpg

If we want ColdFusion to create a new zip archive file no matter what, all we have to do is set the overwrite flag to true (it is false by default). Assuming we have the zip from the above, if we tried to run this code:

  • <!---
  • Zip the mud monster image into the images archive.
  • If the archive already exists, we are going to completely
  • overwrite it with this new archive.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/mud_monster.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • overwrite="true"
  • />

... we will end up with a zip directory structure that looks like this:

./mud_monster.jpg

ColdFusion deleted the existing images.zip archive and create our new one with just the single file.

Notice that all the files that we have zipped are getting placed into the root directory of the archive file. When it comes to adding single files in this manor, this is the default action of the CFZip tag. If we want to store the zipped files into a subdirectory of the archive file, all we have to do is add the Prefix attribute. The following will store the JPG into a subdirectory named images. Assuming we have the zip from the above, if we tried to run this code:

  • <!---
  • Zip the red face image into the images subdirectory of
  • the existing archive.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/red_face.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • prefix="images"
  • />

... we will end up with a zip directory structure that looks like this:

./images/red_face.jpg
./mud_monster.jpg

The prefix doesn't have to be just a single directory; by adding the path slash, the prefix attribute can created nested directories into which we are going to zip the file. Assuming we have the zip from the above, if we tried to run this code:

  • <!---
  • Zip the funny image into the nested goofy subdirectory of
  • the existing archive.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/funny.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • prefix="images/goofy"
  • />

... we will end up with a zip directory structure that looks like this:

./images/goofy/funny.jpg
./images/red_face.jpg
./mud_monster.jpg

Notice that in my code, my prefix uses the forward slash. This is just my preference from working with the web; however, the prefix path will accept both forward and back slashes and act properly (of course, this was only tested on my Windows XP Pro server, so I am not sure how this works across systems).

Since the prefix attribute does create directory paths within the zip archive, the prefix value should follow the directory naming rules of the target system to which you plan to extract the archive. As I am testing on a Windows machine, I cannot include the asterisk in directory or file names. However, as the paths stored in zip archive are not really directories yet, the following code, will not throw any errors:

  • <!---
  • Zip the funny image into the *images* subdirectory of
  • the existing archive.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/images/funny.jpg' )#"
  • file="#ExpandPath( './images.zip' )#"
  • prefix="*images*"
  • />

Notice that the prefix attribute here contains two * symbols. ColdFusion executes this request with no issues. But, when I go to open up the zip archive on my Windows computer, WinRAR gives me this error message:

The following invalid file name was encountered in the archive: *images*\funny.jpg

You can still extract the zip archive, but we will end up with a zip directory structure that looks like this:

./_images_/funny.jpg
./images/goofy/funny.jpg
./images/red_face.jpg
./mud_monster.jpg

Notice that WinRAR changed the asterisk symbols to underscores in order to make the directory path valid in the Window environment. So, it seems that CFZip can handle a wide variety of path characters, but it is up to you to pick characters that are appropriate for your target audience.

Our examples, so far, have only dealt with a single-file source, but CFZip source attribute can also point to a directory. Zipping a directory adds another layer of complexity - sub directories. When it comes to dealing with sub directories, there are two aspects that we need to consider: one is whether we even want to zip the sub directories (recurse the root level directory) and the other is whether we want to store the original file path information within the zip archive.

By default, ColdFusion 8's CFZip tag will zip in a recursive manner and will store the paths of the zipped files. Therefore, if we have the given directory structure:

./data/documents/manual.txt
./data/documents/readme.txt
./data/images/funny.jpg
./data/images/mud_monster.jpg
./data/images/red_face.jpg
./data/images/smile.jpg

... and then we run this CFZip tag, letting it use default values:

  • <!---
  • Zip the entire data directory. By letting ColdFusion
  • use the default values, we should end up with a zip
  • archive that mirrors the path structure of the
  • directory we are zipping.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/' )#"
  • file="#ExpandPath( './data.zip' )#"
  • />

... we will end up with a zip directory structure that looks like this:

./documents/manual.txt
./documents/readme.txt
./images/funny.jpg
./images/mud_monster.jpg
./images/red_face.jpg
./images/smile.jpg

Notice that the directory structure of the zip archive matches the directory structure of the data directory. If we don't want to keep the directory structure, our other option is turn off the StorePath attribute. In doing this, ColdFusion will recurse through the directory structure and zip all files into root directory of the archive. Therefore, running this code on the above directory:

  • <!---
  • Zip the entire data directory. This time, we are not going
  • to store the original file paths. This will get ColdFusion
  • to store all files in the root directory of the zip
  • archive. We are also overwriting the previous zip.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/' )#"
  • file="#ExpandPath( './data.zip' )#"
  • storepath="false"
  • overwrite="true"
  • />

... we will end up with a zip directory structure that looks like this:

./funny.jpg
./manual.txt
./mud_monster.jpg
./readme.txt
./red_face.jpg
./smile.jpg

When you do not let the zip archive store the file path, you run into possible naming conflicts (files of the same name in different directories both moved to the root directory of the zip). If a naming conflict like this does arise, ColdFusion simply overwrites the existing zip entry with the new zip entry of the same name.

If you turn off the recurse feature of the CFZip tag, ColdFusion will only zip the files in the root level of the source directory. Therefore, if we try to run this code:

  • <!---
  • Zip the entire data directory but this time let's not
  • recurse the directory. This will only zip the files that
  • are in the root of the source directory. We are also
  • overwriting the previous zip.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/' )#"
  • file="#ExpandPath( './data.zip' )#"
  • recurse="false"
  • overwrite="true"
  • />

... on the above data directory, we end up getting the ColdFusion error:

Can not create a zip file with no entries. Make sure that there is at least one entry in the zip file.

Since we are not recursing, ColdFusion is attempting to zip only the root level of the source directory. But, if you look above at our source directory, you will see there are no files, just the images and documents sub directories. And, as you can see from the error, ColdFusion will not create a zip that has no files in it.

ColdFusion 8 also gives us the ability to filter files when we are zipping directories. The filter attribute of the CFZip tag takes a comma-delimited list of file masks. The file mask seems to be able to take the wild-card character (asterisk) as well as any literal file character (I could not find much more information on the file filter). The following code will zip the data directory but will only include image files:

  • <!---
  • When we zip the data directory, we are going to recurse
  • through it and only zip files that are a common image
  • extension. We are also overwriting the previous zip.
  • --->
  • <cfzip
  • action="zip"
  • source="#ExpandPath( './data/' )#"
  • file="#ExpandPath( './data.zip' )#"
  • filter="*.JPG,*.GIF,*.PNG"
  • overwrite="true"
  • />

This will result in a zip directory structure that looks like this:

./images/funny.jpg
./images/mud_monster.jpg
./images/red_face.jpg
./images/smile.jpg

Notice that because we let the CFZip tag use many default attribute values, ColdFusion recursed the data directory and stored the original file paths. Also notice that our file extension filter was not case sensitive (our filter used upper case values and matched files with lower case extensions). While I cannot test this, I assume that on a Linux machine where file case does matter, this would be case sensitive (but, of course, that is just a theory).

The file filter works with more than just file extensions; you can use it to filter any part of the file name. This filter, for example:

*red*

... uses multiple wild cards and will only include files who's names contains the phrase "red".

The Prefix attribute that we demonstrated earlier works exactly the same when you zip a directory; instead of storing the files/directories into the root of the zip, the CFZip tag will create a directory with the given prefix and then store all zipped files/directories into that one.

On a final note, we supplied the full path name to all of our target zip files. If you supply a non-expanded zip file archive name, ColdFusion will store the zip in the ColdFusion temporary directory. This is the same directory that can be accessed using the GetTempDirectory() function.

So that covers zipping files and directories using just the ColdFusion 8 CFZip tag. It's pretty amazing how functional this is. And remember, since ColdFusion will, by default, not overwrite an existing zip archive, you can use the CFZip tag multiple times to zip many files and directories into the same archive. If I had one complaint about this awesome new tag, it's that after having worked a lot with the CFImage tag which can create in-memory image objects, it felt strange that CFZip could not create in-memory zip archives. Oh well. In our next part, we will examine how to create zip archives using CFZip in conjunction with the CFZipParam tag; this gives us even more power.




Reader Comments

Great post Ben. You're really saving the folks responsible for the ColdFusion Devnet tutorials at Adobe, a whole lot of time.

This is an awesome new feature. Finally I should be able to build my file manager app that enables me upload my websites in a single zip file that will be extracted, maintaining the proper directory structure, by ColdFusion on a server.

Reply to this Comment

I have to download image files off of a another server and zip them up. I have a url for each image and right now I'm using cfhttp to get the image.

Any idea how to zip them up without writing them out to a tmp directory?

Reply to this Comment

Good article, but, I develope in J2EE and this features is already available for a long time. Since Coldfusion is J2EE based, you can use it before Adobe give it isnt it?

Another question, so is it possible to manipulate AIR archive too?

Reply to this Comment

@Tom,

Yes, you could manipulate zip files before ColdFusion 8, however, no where near as easily and as cleanly as the new CFZip and CFZipParam tags.

As far as AIR archives, I never thought about it, but I guess you can. As far as I have been told, AIR archives are really just ZIP archives with a different file extension.

Reply to this Comment

What about zipping empty folders? When I attempt to do that, the empty folders don't make it into the zip file. (I'm trying to create a zipped template.)

Thanks!

Reply to this Comment

Hello there

I am trying to zip files using cf7 but I got a library and its giving me errors. does anyone have any idea on how to go about this in cf7 as I dont have cf 8
Cheers mates!

Reply to this Comment

Hi Ben, do you happen to know what the maximum number of files in a ZIP-archive and the maxiumum file-size for a ZIP-file created by CF8/CF9 are? Much appreciated!

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.