In my last post about working with zip files, I examined how to list the contents of an archive without using CF8's cfzip tag. Now that we have a list of files, let's look at how to read one of the entries.

To help read the contents of the zip entry, we'll use an input stream to read the bytes of the file, and an output stream to store them for later manipulation. The Java classes involved, in this example, are java.io.FileInputStream, java.util.zip.ZipInputStream, and java.io.ByteArrayOutputStream.

view plain print about
1<cfscript>
2    variables.fileInputStream = createObject("java", "java.io.FileInputStream");
3    variables.byteArrayOutputStream = createObject("java", "java.io.ByteArrayOutputStream");
4    variables.zipInputStream = createObject("java", "java.util.zip.ZipInputStream");
5
</cfscript>

In order to read the bytes of the file, we'll need a byte buffer. Creating one in ColdFusion is pretty simple. The getBytes() method is available via the String class, which is the basis of a ColdFusion string. One space equals one byte.

view plain print about
1local.buffer = repeatString(" ", 1024).getBytes();

Unlike listing the contents of the zip file, where we used java.util.zip.ZipFile, using a FileInputStream and ZipInputStream to read the contents makes working with the raw bytes a little easier:

view plain print about
1local.inputStream = variables.fileInputStream.init( javaCast("string", arguments.filename) );
2local.zipInputStream = variables.zipInputStream.init( local.inputStream );

Once the ZipInputStream is ready, we can use the available() and getNextEntry() methods to iterate over each entry in the zip file. The available() method helps us keep track of the actual data for the entry. As long as data is available, it will return true.

When we find the target entry, we can then start reading in its bytes into a ByteArrayOutputStream. One nice feature of a ByteArrayOutputStream is that it automatically grows as data is read in. Using the 1 KB buffer, we can then add bytes to the output stream, finally saving the bytes for the entire entry in a return variable.

view plain print about
1while ( local.zipInputStream.available() ) {
2    local.entry = local.zipInputStream.getNextEntry();
3    
4    if ( compareNoCase(local.entry.getName(), arguments.entrypath) EQ 0 ) {
5        local.outputStream = variables.byteArrayOutputStream.init();
6        
7        local.chunk = local.zipInputStream.read( local.buffer );
8        
9        while( local.chunk GT 0 ) {
10            local.outputStream.write(local.buffer, 0, local.chunk);
11            local.chunk = local.zipInputStream.read( local.buffer );
12        }
13        
14        local.targetFileContents = local.outputStream.toByteArray();
15        
16        local.outputStream.close();
17    }
18}

Here is all of the code combined into a function for easy reuse:

view plain print about
1<cffunction name="readBinary" access="public" output="false" returntype="binary">
2    <cfargument name="filename" type="string" required="true" hint="The absolute path to the zip file.">
3    <cfargument name="entrypath" type="string" required="true" hint="The path to the target file within the zip file.">
4    <cfscript>
5        var local = structNew();
6        
7        local.buffer = repeatString(" ", 1024).getBytes();
8        local.targetFileContents = 0;
9        
10        variables.fileInputStream = createObject("java", "java.io.FileInputStream");
11        variables.byteArrayOutputStream = createObject("java", "java.io.ByteArrayOutputStream");
12        variables.zipInputStream = createObject("java", "java.util.zip.ZipInputStream");
13        
14        try {
15            local.inputStream = variables.fileInputStream.init( javaCast("string", arguments.filename) );
16            local.zipInputStream = variables.zipInputStream.init( local.inputStream );
17            
18            while ( local.zipInputStream.available() ) {
19                local.entry = local.zipInputStream.getNextEntry();
20                
21                if compareNoCasese(local.entry.getName(), arguments.entrypath) EQ 0 ) {
22                    local.outputStream = variables.byteArrayOutputStream.init();
23                    
24                    local.chunk = local.zipInputStream.read( local.buffer );
25                    
26                    while( local.chunk GT 0 ) {
27                        local.outputStream.write(local.buffer, 0, local.chunk);
28                        local.chunk = local.zipInputStream.read( local.buffer );
29                    }
30                    
31                    local.targetFileContents = local.outputStream.toByteArray();
32                    
33                    local.outputStream.close();
34                }
35            }
36            
37            local.inputStream.close();
38            local.zipInputStream.close();
39        }
40        catch (any e) {
41            throw(e);
42        }
43        
44        return local.targetFileContents;
45    
</cfscript>
46</cffunction>