Skip to main content
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Joe Rinehart
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Joe Rinehart ( @joeRinehart )

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

By on
Tags:

In the first part of this tutorial, we explored the ways in which the ColdFusion 8 CFZip tag, when used alone, can zip both files and entire directories into archives. Now, let's take a look at the ColdFusion 8 CFZipParam tag in the context of archive creation. The CFZipParam tag is a child of the CFZip tag and gives us even more power over what kind of data is added to the archive and how those entries are stored.

In Part I, we started off zipping a single file with CFZip; now, let's start off Part II zipping a single file with CFZipParam:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single file. The file will be stored in
		the archive as the root directory with the same
		file name it already has (readme.txt).
	--->
	<cfzipparam
		source="#ExpandPath( './data/documents/readme.txt' )#"
		/>

</cfzip>

Here, the CFZipParam tag is the only child tag of the CFZip tag. Notice that, instead of providing the source attribute of the CFZip tag, as we would have done previously, we are providing the source attribute through the CFZipParam tag. By default, ColdFusion will store this file in the root directory of the archive with the same name as the file as it exists. This will result in a zip directory structure that looks like this:

./readme.txt

Both the CFZip and the CFZipParam tags have a source attribute. And, when it comes to using both tags in conjunction, it's not one or the other - the Source attributes of the two tags also work in conjunction. If the CFZip tag defines a source directory, then the Source attributes of the nested CFZipParam tags are relative to that parent directory. Running the following code:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/documents/' )#"
	overwrite="true">

	<!---
		Add a single file. The file will be stored in
		the archive as the root directory with the same
		file name it already has (readme.txt). Since the
		source attribute was defined in the CFZip tag,
		the source attribute of the CFZipParam tag is
		relative the previous source value.
	--->
	<cfzipparam
		source="readme.txt"
		/>

</cfzip>

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

./readme.txt

In the above example, ColdFusion took the Source attribute of the CFZip tag, "./data/documents/", and the Source attribute of the CFZip tag, "readme.txt", and created a combined path of "./data/documents/readme.txt." Now, in Part I of our tutorial, we demonstrated that if you provide a directory as the Source attribute, ColdFusion will archive the entire directory (by default). Notice, that in the above example, ColdFusion did not archive the directory, but rather just the CFZipParam file.

If you choose to use the Source attribute of the CFZip tag, all of your CFZipParam tags must use relative source attributes. You cannot mix and match. If you try to use a CFZip source and an absolute path for the CFZipParam tag, ColdFusion will throw an error. Either you use both, or you use just the CFZipParam source attribute.

We don't have to let the file be archived with the same name. We can rename it using the EntryPath attribute of the CFZipParam tag. Running this code:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single file. The file will be stored in
		the archive as the root directory but, instead of
		using the existing file name, the file will be
		stored as read_me_first.txt.
	--->
	<cfzipparam
		source="#ExpandPath( './data/documents/readme.txt' )#"
		entrypath="read_me_first.txt"
		/>

</cfzip>

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

./read_me_first.txt

In the above, all we defined was the new file name; however, like the prefix attribute of the CFZip tag, the EntryPath attribute can also define a directory structure. By adding slashes to the attribute value, ColdFusion will create directories in which to save the file. Running this code:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single file. Instead of storing it in the
		root directory, we are going to store it in a
		new, nested directory.
	--->
	<cfzipparam
		source="#ExpandPath( './data/documents/readme.txt' )#"
		entrypath="manuals/_LOOK_HERE_FIRST_/readme.txt"
		/>

</cfzip>

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

./manuals/_LOOK_HERE_FIRST_/readme.txt

Notice that we are not only setting the file name, we are also defining which directory that file is stored in.

I mentioned above that the ColdFusion 8 CFZip tag has a prefix attribute; well, the CFZipParam tag also has a Prefix attribute. And, just like the CFZip tag, the Prefix attribute of this CFZipParam tag defines the directory in which the file is stored. We can reproduce the above structure using the Prefix attribute. Running the code:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single file. Instead of storing it in the
		root directory, we are going to store it in a
		new, nested directory at the given prefix.
	--->
	<cfzipparam
		source="#ExpandPath( './data/documents/readme.txt' )#"
		prefix="manuals/_LOOK_HERE_FIRST_"
		/>

</cfzip>

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

./manuals/_LOOK_HERE_FIRST_/readme.txt

Notice that since we did not include any EntryPath information, the file is stored using its original file name. You might be tempted to try and use the EntryPath to define the file name in conjunction with the Prefix to define the storage directory, as in this example:

<!---
	Create a zip archive that contains a single
	file. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single file. Instead of storing it in the
		root directory, we are going to store it in a
		new, nested directory at the given prefix stored
		at the given entry path file name.
	--->
	<cfzipparam
		source="#ExpandPath( './data/documents/readme.txt' )#"
		prefix="manuals/_LOOK_HERE_FIRST_"
		entrypath="read_me_text.txt"
		/>

</cfzip>

ColdFusion will not throw any sort of error here, but it will not work the way you expect it to. EntryPath takes precedence over the Prefix attribute. In fact, when the EntryPath is present, the Prefix value is completely ignored. Therefore, running the code above will result in a zip directory structure that looks like this:

./read_me_text.txt

Notice that since the EntryPath did not include any sub directory information, the file entry is stored in the root directory of the zip archive (even though the Prefix defined some directory nesting). The EntryPath could have, just as we demonstrated earlier, included the sub directory definition.

One of the coolest things about the CFZipParam tag is that it allows us to archive more than just files off of the server; we can also archive text and binary data using the Content attribute. In this next example, we are going to store a string directly into a text entry of the archive:

<!---
	Create a zip archive that contains a single
	entry. This entry will be created from text
	data, not from a file on the server. We are
	going to overwrite any previously existing
	archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single entry using the given text
		data into the given entry path.
	--->
	<cfzipparam
		entrypath="text_entry.txt"
		content="This is my text entry!"
		/>

</cfzip>

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

./text_entry.txt

... and that text file contains the text data from the Content attribute above. When storing text data, ColdFusion uses the CharSet attribute the string into binary data. This attribute defaults to whatever the default encoding of the machine is. I don't full understand this, but not including the attribute seems to work nicely.

Binary data can be stored in exactly the same way. Running this code:

<!--- Read in the image file as binary data. --->
<cffile
	action="readbinary"
	file="#ExpandPath( './data/images/mud_monster.jpg' )#"
	variable="binImage"
	/>

<!---
	Create a zip archive that contains a single
	entry. This entry will be created from binary
	image data, not from a file on the server. We
	are going to overwrite any previously existing
	archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Add a single entry using the given image binary
		data into the given entry path.
	--->
	<cfzipparam
		entrypath="mud_monster.jpg"
		content="#binImage#"
		/>

</cfzip>

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

./mud_monster.jpg

When you use the Content attribute, there is no original file on which to base the entry's file name. Therefore, if you do store string or binary data, you must use the EntryPath attribute so that ColdFusion knows how to store the target file.

Ok, so now let's talk about directories. CFZipParam can archive full or partial directories as well as single files. In the following examples, we are going to assume we have the following data directory:

./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

Now, we can archive the directory using CFZipParam:

<!---
	Create a zip archive that contains a multiple
	entries. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	overwrite="true">

	<!---
		Zip the entire images directory at the given
		source path.
	--->
	<cfzipparam
		source="#ExpandPath( './data/images/' )#"
		/>

</cfzip>

Running the above will result in a zip directory structure that looks like this:

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

In this case, we were using an expanded path to point the directory. However, just as with the single file example way above, the Source attribute of the CFZip and CFZipParams work together when zipping directories. In the next example, we will define the CFZipParam's Source directory as being relative to the CFZip's Source attribute:

<!---
	Create a zip archive that contains a multiple
	entries. We are going to overwrite any previously
	existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/images/' )#"
	overwrite="true">

	<!---
		Zip the entire directory. Since the CFZip tag defined
		a source attribute, that means the source attribute
		of the CFZipParam tag will relative to it. Since we
		want to zip the entire directory, just leave the
		source attribute empty.
	--->
	<cfzipparam
		source=""
		/>

</cfzip>

Notice that since the CFZip tag defines the actual directory we are trying to archive, the CFZipParam tag's Source attribute doesn't need a value at all. Running the above will result in a zip directory structure that looks like this:

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

Because the Source attribute of the CFZipParam tag is relative, we could have also used the value:

"./"

This would put us into the same directory. If you are familiar with this type of path notation, then you would also know that:

"../"

... moves you up a directory. The CFZipParam tag recognizes this as a valid relative path directive. In fact, if we re-ran the above code example using "../" instead of "./", we would end up with an archive that contained both the documents and images directories. The "../" would have taken us out of the images directory and into the root Data directory. Then, since CFZipParam recurses through directories by default, it would have zipped both the nested images and documents sub directories.

Just like the CFZip tag, when dealing with directories, the CFZipParam tag has the Filter, Recurse, and Prefix attributes. These act in exactly the same way as they did in the CFZip tag. I am not going to into detail here since the filter and recurse features were covered extensively in Part I of this tutorial and the Prefix attribute was covered in both Part I as well as above.

ColdFusion 8's new CFZipParam works with both files and directories, but this doesn't mean you have to use one or the other. The CFZip tag can have multiple CFZipParam child tags, each of which may deal with a file or a directory. In our next example, we will zip up the entire Data directory, but using multiple CFZipParam tags:

<!---
	Create a zip archive that contains the entire Data
	directory. We are going to use the CFZip's source
	attribute to put us in the correct base directory
	and then each child CFZipParam tag will use a source
	relative to that. We are going to overwrite any
	previously existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/' )#"
	overwrite="true">

	<!---
		Zip the entire documents directory. This
		will create a documents directory in the
		root of the archive.
	--->
	<cfzipparam
		source="./documents/"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./images/funny.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./images/mud_monster.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./images/red_face.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./images/smile.jpg"
		/>

</cfzip>

Running the above code will result in a zip directory structure that looks like this:

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

Notice that the documents directory gets created as a root-level directory - it's relative source path was maintained within the archive - but the images were zipped directly into the root of the archive - their relative source paths were not maintained. This is slightly different behavior than the zipping of a directory using just the CFZip tag. If all we used was the CFZip tag with a source directory, the files of the source directory would be zipped directly into the archive root.

Since the CFZipParam tag doesn't have a StorePath attribute (like the CFZip tag), if we wanted to put all of the files into the archive root, we have two options (not including the one that doesn't use the CFZipParam tag at all): we could change the source attribute of the CFZip tag to start in the documents folder:

<!---
	Create a zip archive that contains the entire Data
	directory. We are going to use the CFZip's source
	attribute to put us in the documents base directory
	and then each child CFZipParam tag will use a source
	relative to that. We are going to overwrite any
	previously existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/documents/' )#"
	overwrite="true">

	<!---
		Zip the entire documents directory.
	--->
	<cfzipparam
		source="./"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="../images/funny.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="../images/mud_monster.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="../images/red_face.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="../images/smile.jpg"
		/>

</cfzip>

Notice that this time, the Source attribute of the CFZip tag puts us into the Documents sub directory. The CFZipParam tag that zips the documents directory now just has "./" to denote the current directory. The CFZipParam tags that zip the images now just have to go up a directory to be able to access the images folder. This will put all files into the root archive since the file-only CFZipParam tags do that by default and the directory CFZipParam tag doesn't really have a path, so its contents get put in the archive root.

Our other option is to break the zip archive creation into two CFZip tags:

<!---
	Create a zip archive that contains the entire Data
	directory. We are going to use the CFZip's source
	attribute to put us in the images base directory
	and then each child CFZipParam tag will use a source
	relative to that. We are going to overwrite any
	previously existing archive of the same name.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/images/' )#"
	overwrite="true">

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./funny.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./mud_monster.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./red_face.jpg"
		/>

	<!--- Zip one of the images into the archive root. --->
	<cfzipparam
		source="./smile.jpg"
		/>

</cfzip>


<!---
	Now that we have a zip archive that has the images,
	let's add the contents of the documents folder to the
	exisitng zip archive root. This time, we have no need
	to overwrite the existing archive.
--->
<cfzip
	action="zip"
	file="#ExpandPath( './data.zip' )#"
	source="#ExpandPath( './data/documents/' )#"
	/>

Notice that this time, the first CFZip tag's Source attribute puts us into the Images directory. Each of the child CFZipParam tags then provides a directory-relative path to the image. The second CFZip tag then zips the documents directory into the existing archive created by the first CFZip tag. Pretty cool stuff. Running either code examples will result in a zip directory structure that looks like this:

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

The ColdFusion 8 CFZip tag gives us a lot of functionality. The CFZipParam tag gives us even more functionality with the ability to store text entries and binary entries and run multiple actions on a single archive. Used together, you can really see how easy ColdFusion 8 is making it for us developers to create zip archives.

Want to use code from this post? Check out the license.

Reader Comments

32 Comments

Hello Ben, good post!!!!
Now, is it possible to manually select the files I want to zip? Let's say I list all the files in a directory. So I need to zip, from that directory file1.eps, file2.eps and file.ai

Can I do that by using cfselect multiple and then passing the values to CFZipParam with the selection from my form?

Thank you much!

2 Comments

how do i know it is zipped?
like what if i zip more 10mg then it takes time...
so is there any way to find out it's zipping or zipped?

15,640 Comments

@Sangyong,

CFZip doesn't execute asynchronously; when you call the CFZip tag, your thread of execution will halt at that command until the files are zipped. You don't have to add any additional logic to detect when something is done zipping.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel