This example
demonstrates how to read a simple .zip file containing several files of
different types. It does so by extracting relevant data from the
metadata for each file, uncompressing each file into a ByteArray
and writing the file to the desktop.
The general structure of a .zip file is based on the specification
by PKWARE Inc., which is maintained at
http://www.pkware.com/documents/casestudies/APPNOTE.TXT
.
First is a file header and file data for the first file in the .zip
archive, followed by a file header and file data pair for each additional
file. (The structure of the file header is described later.) Next,
the .zip file optionally includes a data descriptor record (usually
when the output zip file was created in memory rather than saved
to a disk). Next are several additional optional elements: archive
decryption header, archive extra data record, central directory
structure, Zip64 end of central directory record, Zip64 end of central
directory locator, and end of central directory record.
The code in this example is written to only parse zip files that
do not contain folders and it does not expect data descriptor records.
It ignores all information following the last file data.
The format of the file header for each file is as follows:
file header signature
|
4 bytes
|
required version
|
2 bytes
|
general-purpose bit flag
|
2 bytes
|
compression method
|
2 bytes (8=DEFLATE; 0=UNCOMPRESSED)
|
last modified file time
|
2 bytes
|
last modified file date
|
2 bytes
|
crc-32
|
4 bytes
|
compressed size
|
4 bytes
|
uncompressed size
|
4 bytes
|
file name length
|
2 bytes
|
extra field length
|
2 bytes
|
file name
|
variable
|
extra field
|
variable
|
Following the file header is the actual file data, which can
be either compressed or uncompressed, depending on the compression
method flag. The flag is 0 (zero) if the file data is uncompressed,
8 if the data is compressed using the DEFLATE algorithm, or another
value for other compression algorithms.
The user interface for this example consists of a label and a
text area (
taFiles
). The application writes the
following information to the text area for each file it encounters
in the .zip file: the file name, the compressed size, and the uncompressed
size. The following MXML document defines the user interface for
the Flex version of the application:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">
<mx:Script>
<![CDATA[
// The application code goes here
]]>
</mx:Script>
<mx:Form>
<mx:FormItem label="Output">
<mx:TextArea id="taFiles" width="320" height="150"/>
</mx:FormItem>
</mx:Form>
</mx:WindowedApplication>
The beginning of the program performs the following tasks:
-
Imports
the required classes
import flash.filesystem.*;
import flash.utils.ByteArray;
import flash.events.Event;
-
Defines
the user interface for Flash
import fl.controls.*;
//requires TextArea and Label components in the Library
var taFiles = new TextArea();
var output = new Label();
taFiles.setSize(320, 150);
taFiles.move(10, 30);
output.move(10, 10);
output.width = 150;
output.text = "Contents of HelloAir.zip";
addChild(taFiles);
addChild(output);
-
Defines the
bytes
ByteArray
var bytes:ByteArray = new ByteArray();
-
Defines variables to store metadata from the file header
// variables for reading fixed portion of file header
var fileName:String = new String();
var flNameLength:uint;
var xfldLength:uint;
var offset:uint;
var compSize:uint;
var uncompSize:uint;
var compMethod:int;
var signature:int;
-
Defines File (
zfile
) and FileStream (
zStream
)
objects to represent the .zip file, and specifies the location of
the .zip file from which the files are extracted—a file named “HelloAIR.zip”
in the desktop directory.
// File variables for accessing .zip file
var zfile:File = File.desktopDirectory.resolvePath("HelloAIR.zip");
var zStream:FileStream = new FileStream();
In Flex, the program code starts in the
init()
method,
which is called as the
creationComplete
handler
for the root
mx:WindowedApplication
tag.
// for Flex
private function init():void
{
The program begins by opening the .zip file in READ mode.
zStream.open(zfile, FileMode.READ);
It then sets the
endian
property of
bytes
to
LITTLE_ENDIAN
to
indicate that the byte order of numeric fields has the least significant
byte first.
bytes.endian = Endian.LITTLE_ENDIAN;
Next, a
while()
statement begins a loop that
continues until the current position in the file stream is greater
than or equal to the size of the file.
while (zStream.position < zfile.size)
{
The first statement inside the loop reads the first 30 bytes
of the file stream into the ByteArray
bytes
. The
first 30 bytes make up the fixed-size part of the first file header.
// read fixed metadata portion of local file header
zStream.readBytes(bytes, 0, 30);
Next, the code reads an integer (
signature
)
from the first bytes of the 30-byte header. The ZIP format definition
specifies that the signature for every file header is the hexadecimal
value
0x04034b50
; if the signature is different
it means that the code has moved beyond the file portion of the
.zip file and there are no more files to extract. In that case the
code exits the
while
loop immediately rather than
waiting for the end of the byte array.
bytes.position = 0;
signature = bytes.readInt();
// if no longer reading data files, quit
if (signature != 0x04034b50)
{
break;
}
The next part of the code reads the header byte at offset position
8 and stores the value in the variable
compMethod
.
This byte contains a value indicating the compression method that
was used to compress this file. Several compression methods are
allowed, but in practice nearly all .zip files use the DEFLATE compression
algorithm. If the current file is compressed with DEFLATE compression,
compMethod
is
8; if the file is uncompressed,
compMethod
is 0.
bytes.position = 8;
compMethod = bytes.readByte(); // store compression method (8 == Deflate)
Following the first 30 bytes is a variable-length portion of
the header that contains the file name and, possibly, an extra field.
The variable
offset
stores the size of this portion.
The size is calculated by adding the file name length and extra
field length, read from the header at offsets 26 and 28.
offset = 0; // stores length of variable portion of metadata
bytes.position = 26; // offset to file name length
flNameLength = bytes.readShort(); // store file name
offset += flNameLength; // add length of file name
bytes.position = 28; // offset to extra field length
xfldLength = bytes.readShort();
offset += xfldLength; // add length of extra field
Next the program reads the variable-length portion of the file
header for the number of bytes stored in the
offset
variable.
// read variable length bytes between fixed-length header and compressed file data
zStream.readBytes(bytes, 30, offset);
The
program reads the file name from the variable length portion of
the header and displays it in the text area along with the compressed
(zipped) and uncompressed (original) sizes of the file.
// Flash version
bytes.position = 30;
fileName = bytes.readUTFBytes(flNameLength); // read file name
taFiles.appendText(fileName + "\n"); // write file name to text area
bytes.position = 18;
compSize = bytes.readUnsignedInt(); // store size of compressed portion
taFiles.appendText("\tCompressed size is: " + compSize + '\n');
bytes.position = 22; // offset to uncompressed size
uncompSize = bytes.readUnsignedInt(); // store uncompressed size
taFiles.appendText("\tUncompressed size is: " + uncompSize + '\n');
// Flex version
bytes.position = 30;
fileName = bytes.readUTFBytes(flNameLength); // read file name
taFiles.text += fileName + "\n"; // write file name to text area
bytes.position = 18;
compSize = bytes.readUnsignedInt(); // store size of compressed portion
taFiles.text += "\tCompressed size is: " + compSize + '\n';
bytes.position = 22; // offset to uncompressed size
uncompSize = bytes.readUnsignedInt(); // store uncompressed size
taFiles.text += "\tUncompressed size is: " + uncompSize + '\n';
The example reads the rest of the file from the file stream into
bytes
for
the length specified by the compressed size, overwriting the file
header in the first 30 bytes. The compressed size is accurate even
if the file is not compressed because in that case the compressed
size is equal to the uncompressed size of the file.
// read compressed file to offset 0 of bytes; for uncompressed files
// the compressed and uncompressed size is the same
if (compSize == 0) continue;
zStream.readBytes(bytes, 0, compSize);
Next, the example uncompresses the compressed file and calls
the
outfile()
function to write it to the output
file stream. It passes
outfile()
the file name and
the byte array containing the file data.
if (compMethod == 8) // if file is compressed, uncompress
{
bytes.uncompress(CompressionAlgorithm.DEFLATE);
}
outFile(fileName, bytes); // call outFile() to write out the file
In
the previously mentioned example,
bytes.uncompress(CompressionAlgorithm.DEFLATE)
will
work only in AIR applications. To get deflated data uncompressed
for both AIR and Flash Player, invoke ByteArray’s
inflate()
function.
The
closing braces indicate the end of the
while
loop,
and of the
init()
method and the Flex application
code, except for the
outFile()
method. Execution
loops back to the beginning of the
while
loop and
continues processing the next bytes in the .zip file—either extracting another
file or ending processing of the .zip file if the last file has
been processed.
} // end of while loop
} // for Flex version, end of init() method and application
The
outfile()
function
opens an output file in WRITE mode on the desktop, giving it the
name supplied by the
filename
parameter. It then
writes the file data from the
data
parameter to
the output file stream (
outStream
) and closes the
file.
// Flash version
function outFile(fileName:String, data:ByteArray):void
{
var outFile:File = File.desktopDirectory; // destination folder is desktop
outFile = outFile.resolvePath(fileName); // name of file to write
var outStream:FileStream = new FileStream();
// open output file stream in WRITE mode
outStream.open(outFile, FileMode.WRITE);
// write out the file
outStream.writeBytes(data, 0, data.length);
// close it
outStream.close();
}
private function outFile(fileName:String, data:ByteArray):void
{
var outFile:File = File.desktopDirectory; // dest folder is desktop
outFile = outFile.resolvePath(fileName); // name of file to write
var outStream:FileStream = new FileStream();
// open output file stream in WRITE mode
outStream.open(outFile, FileMode.WRITE);
// write out the file
outStream.writeBytes(data, 0, data.length);
// close it
outStream.close();
}