The ZipReader class lets an application read a Zip archive without resorting to intermediate storage (either memory or disk). Furthermore, extraction from the Zip archive starts as soon as the ZipReader object has data to decompress: the decompressed files are immediately available for processing, for example by an application using a ZipWriter object sending the archive over an FTP connection. The target Zip archive is passed to the ZipReader's class constructor as a stream of any type.
The main methods of the ZipReader class used to extract data from Zip archives are ReadItemLocalHeader and ReadItemData. The class also provides ByteProgression and InvalidPassword events to monitor the extraction operation. For details on the events used by Xceed Real-Time Zip for .NET / .NET CF, see Events.
Xceed Real-Time Zip for .NET does not support reading split/spanned or self-extracting archives, or archives compressed inside another archive.
The Stored compression method and the None compression level are only supported on zip files that do not use the Zip64 format extensions designed to support zip items or zip archives larger than 4GB. Zip utilities (i.e., WinZip) will only use the Zip64 format extensions when necessary (with very large files that break the 4GB barrier). So even though these utilities will sometimes choose to store items rather than compress them, the ZipReader class should still be able to read these archives.
Using the ZipReader Class
The following diagram will help to illustrate the relationship between the structure of a Zip archive and the class methods you must use to extract items from a Zip archive.
Red boxes: Local header Blue boxes: Compressed data Green boxes: Data descriptor Grey boxes: Central headers that make up the central directory
When extracting files from a Zip archive, a stream representing the source archive is first passed to the ZipReader constructor. A password can optionally be passed.
The data is then accessed by alternately calling ReadItemLocalHeader and ReadItemData until no more local headers are found (when null is returned by ReadItemLocalHeader). The ReadItemData method is very similar to the Read method of the Stream class: the data is retrieved into an array of bytes, with an offset indicating at which point to begin storing the data read from the Zip archive and a count indicating maximum number of bytes to be read.
There are several flavors of ReadItemData to accommodate different data scenarios and make efficient reuse of data buffers.
Another way to read data is to expose the item's data as a read-only Stream object with the GetItemDataStream method. Each call to the stream's Read method will call ReadItemData. This makes it possible to integrate ZipReader with other classes that use the Stream class interface without the need for "glue code."
Note that if the ZipReader encounters a ZipItemLocalHeader representing a folder, the header will not be returned by ReadItemLocalHeader. ZipWriter does not support writing folders as items in a Zip archive.
Make sure the Stream given to ZipReader is already positioned at the beginning of the zip archive. If the stream is seekable, a call to Stream.Seek( 0, SeekOrigin.Begin ) is usually sufficient.
Example: Extracting data from a Zip archive using ZipReader on desktop environments
The following example shows how to read a Zip archive on desktop environments.
staticvoid ZipReaderExample()
{
string zipFilePath = @"D:\RealTimeZipExamples\MyZipFile.zip";
string destinationFolder = @"D:\UnzipToFolder";
string password = "password";
int bufferSize = 64 * 1024;
byte[] buffer = newbyte[ bufferSize ];
// Create a stream for the zip file
using( Stream zipFileStream = new FileStream( zipFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan ) )
{
// Create a local header object
ZipItemLocalHeader localHeader = new ZipItemLocalHeader();
// Create the ZipReader object around the stream
using( ZipReader zipReader = new ZipReader( zipFileStream ) )
{
// Optional. Provide the default password for encrypted items in the archive
zipReader.EncryptionPassword = password;
// Optional. Subscribe to available events
zipReader.ByteProgression += new EventHandler<ZipReaderByteProgressionEventArgs>( OnByteProgression );
zipReader.InvalidPassword += new EventHandler<ZipReaderInvalidPasswordEventArgs>( OnInvalidPassword );
// While the reader finds local headers
while( zipReader.ReadItemLocalHeader( localHeader ) != null )
{
// The 'FileName' property contains the sub-folders and filename
string outputPath = destinationFolder + localHeader.FileName;
string outputFolder = Path.GetDirectoryName( outputPath );
// Make sure the output folder exists
Directory.CreateDirectory( outputFolder );
// If the item isn't a folder entry
if( !localHeader.IsFolder )
{
// Create/overwrite an output file using our calculated filename
using( FileStream outputFileStream = new FileStream( outputPath, FileMode.Create, FileAccess.Write, FileShare.None ) )
{
// Have the reader read the item data and write it to the stream using our buffer
zipReader.ReadItemData( outputFileStream, buffer, 0, bufferSize );
}
}
}
}
}
}
privatestaticvoid OnInvalidPassword( object sender, ZipReaderInvalidPasswordEventArgs e )
{
// TODO: We have access to the current item being unzipped. We can report it's name, etc
ZipItemLocalHeader currentItem = e.ZipItemLocalHeader;
// TODO: We're given the password that failed. We can report it, etc
string oldPassword = e.OldPassword;
/* If 'e.NewPassword' is set to null or an empty string, or 'e.Abort' is set to true,
a ZipReaderException will be thrown for failure to decrypt the item.
Since items can't be skipped, the entire unzip process will be canceled.
When the event is triggered, 'e.NewPassword' is set to an empty string and 'e.Abort' is set
to false. */// TODO: We have to supply a new password.
// If that new password is invalid, the event will be triggered again
e.NewPassword = "Some New Password";
// TODO: We can ask the entire unzip operation to be aborted
e.Abort = true;
}
privatestaticvoid OnByteProgression( object sender, ZipReaderByteProgressionEventArgs e )
{
// TODO: We have access to the current item being unzipped. We can report it's name, etc
ZipItemLocalHeader currentItem = e.ZipItemLocalHeader;
// TODO: We're given the current amount of bytes unzipped for the item. We can report it, etc
long bytesProcessed = e.BytesProcessed;
/* Do not assume that e.UncompressedSize and e.Percent will contain useful values.
Since ZipReader doesn't seek in the archive, it cannot always know the uncompressed
size in advance. */
}
PrivateSharedSub ZipReaderExample()
Dim zipFilePath AsString = "D:\RealTimeZipExamples\MyZipFile.zip"Dim destinationFolder AsString = "D:\UnzipToFolder"Dim password AsString = "password"Dim bufferSize AsInteger = 64 * 1024
Dim buffer(bufferSize - 1) AsByte' Create a stream for the zip file
Using zipFileStream As Stream = New FileStream(zipFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, FileOptions.SequentialScan)
' Create a local header object
Dim localHeader AsNew ZipItemLocalHeader()
' Create the ZipReader object around the stream
Using zipReader AsNew ZipReader(zipFileStream)
' Optional. Provide the default password for encrypted items in the archive
zipReader.EncryptionPassword = password
' Optional. Subscribe to available events
AddHandler zipReader.ByteProgression, AddressOf OnByteProgression
AddHandler zipReader.InvalidPassword, AddressOf OnInvalidPassword
' While the reader finds local headers
DoWhile zipReader.ReadItemLocalHeader(localHeader) IsNotNothing' The 'FileName' property contains the sub-folders and filename
Dim outputPath AsString = destinationFolder & localHeader.FileName
Dim outputFolder AsString = Path.GetDirectoryName(outputPath)
' Make sure the output folder exists
Directory.CreateDirectory(outputFolder)
' If the item isn't a folder entry
If (Not localHeader.IsFolder) Then' Create/overwrite an output file using our calculated filename
Using outputFileStream AsNew FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.None)
' Have the reader read the item data and write it to the stream using our buffer
zipReader.ReadItemData(outputFileStream, buffer, 0, bufferSize)
EndUsingEndIfLoopEndUsingEndUsingEnd SubPrivateSharedSub OnInvalidPassword(ByVal sender AsObject, ByVal e As ZipReaderInvalidPasswordEventArgs)
' TODO: We have access to the current item being unzipped. We can report it's name, etc
Dim currentItem As ZipItemLocalHeader = e.ZipItemLocalHeader
' TODO: We're given the password that failed. We can report it, etc
Dim oldPassword AsString = e.OldPassword
' If 'e.NewPassword' is set to null or an empty string, or 'e.Abort' is set to true,
' a ZipReaderException will be thrown for failure to decrypt the item.
' Since items can't be skipped, the entire unzip process will be canceled.
'
' When the event is triggered, 'e.NewPassword' is set to an empty string and 'e.Abort' is set
' to false.
' TODO: We have to supply a new password.
' If that new password is invalid, the event will be triggered again
e.NewPassword = "Some New Password"' TODO: We can ask the entire unzip operation to be aborted
e.Abort = TrueEnd SubPrivateSharedSub OnByteProgression(ByVal sender AsObject, ByVal e As ZipReaderByteProgressionEventArgs)
' TODO: We have access to the current item being unzipped. We can report it's name, etc
Dim currentItem As ZipItemLocalHeader = e.ZipItemLocalHeader
' TODO: We're given the current amount of bytes unzipped for the item. We can report it, etc
Dim bytesProcessed AsLong = e.BytesProcessed
' Do not assume that e.UncompressedSize and e.Percent will contain useful values.
' Since ZipReader doesn't seek in the archive, it cannot always know the uncompressed
' size in advance.
End Sub
Example: Extracting data from a Zip archive using ZipReader on Xamarin (Android and iOS)
The following example shows how to read a Zip archive on Xamarin (Android and iOS).
staticvoid ZipReaderExampleXamarin( Stream zipFileStream )
{
string password = "password";
/* If we'll be extracting many items from the archive, we'll create a work buffer
ahead of time and use it over and over again to avoid creating a new buffer
each time we write to a destination. */int bufferSize = 64 * 1024;
byte[] buffer = newbyte[ bufferSize ];
Dictionary<string, MemoryStream> extractedFiles = new Dictionary<string, MemoryStream>();
/* NOTE: The zip file can be any type of stream you use. A network stream, a file stream, etc
The component will never call Stream.Seek() on the stream and will only read to it.
This example will use the 'zipFileStream' memory stream used in the ZipWriter example. */// Make sure the zip file stream is positioned at the start
zipFileStream.Seek( 0, SeekOrigin.Begin );
// Create a local header object that will be reused
ZipItemLocalHeader localHeader = new ZipItemLocalHeader();
// Create the ZipReader object around the stream
using( ZipReader zipReader = new ZipReader( zipFileStream ) )
{
/* As a convenience, ZipReader offers the option to automatically close the input stream when
ZipReader is closed. The behavior is disabled by default.
In this example, we will not enable it as we may want to manipulate the zip file
after it has been read. *///zipReader.AllowInputStreamClosure = true;
// Optional. Provide the default password for encrypted items in the archive
zipReader.EncryptionPassword = password;
// Optional. Subscribe to available events
zipReader.ByteProgression += new EventHandler<ZipReaderByteProgressionEventArgs>( OnByteProgression );
zipReader.InvalidPassword += new EventHandler<ZipReaderInvalidPasswordEventArgs>( OnInvalidPassword );
/* Here, we supply the local header object we created above.
ReadItemLocalHeader() will fill the object with current data and return a
reference to the same object.
ReadItemLocalHeader() can also be called without parameters. Then, it will
create a new ZipItemLocalHeader object and return it with current data. */// While the reader finds local headers
while( zipReader.ReadItemLocalHeader( localHeader ) != null )
{
/* NOTE: 'localHeader.FileName' will contain a leading slash */// If the item isn't a folder entry
if( !localHeader.IsFolder )
{
/* NOTE: The destination data can be any type of stream you need. A network stream, a file stream, etc
The component will never call Stream.Seek() on the stream and will only write to it.
This example will use memory data to keep it simple and on point. */
MemoryStream outputStream = new MemoryStream();
extractedFiles.Add( localHeader.FileName, outputStream );
// Have the reader read the item data and write it to the stream using our buffer
zipReader.ReadItemData( outputStream, buffer, 0, bufferSize );
}
}
}
}
privatestaticvoid OnInvalidPassword( object sender, ZipReaderInvalidPasswordEventArgs e )
{
// TODO: We have access to the current item being unzipped. We can report it's name, etc
ZipItemLocalHeader currentItem = e.ZipItemLocalHeader;
// TODO: We're given the password that failed. We can report it, etc
string oldPassword = e.OldPassword;
/* If 'e.NewPassword' is set to null or an empty string, or 'e.Abort' is set to true,
a ZipReaderException will be thrown for failure to decrypt the item.
Since items can't be skipped, the entire unzip process will be canceled.
When the event is triggered, 'e.NewPassword' is set to an empty string and 'e.Abort' is set
to false. */// TODO: We have to supply a new password.
// If that new password is invalid, the event will be triggered again
e.NewPassword = "Some New Password";
// TODO: We can ask the entire unzip operation to be aborted
e.Abort = true;
}
privatestaticvoid OnByteProgression( object sender, ZipReaderByteProgressionEventArgs e )
{
// TODO: We have access to the current item being unzipped. We can report it's name, etc
ZipItemLocalHeader currentItem = e.ZipItemLocalHeader;
// TODO: We're given the current amount of bytes unzipped for the item. We can report it, etc
long bytesProcessed = e.BytesProcessed;
/* Do not assume that e.UncompressedSize and e.Percent will contain useful values.
Since ZipReader doesn't seek in the archive, it cannot always know the uncompressed
size in advance. */
}
PrivateSharedSub ZipReaderExampleXamarin(ByVal zipFileStream As Stream)
Dim password AsString = "password"' If we'll be extracting many items from the archive, we'll create a work buffer
' ahead of time and use it over and over again to avoid creating a new buffer
' each time we write to a destination.
Dim bufferSize AsInteger = 64 * 1024
Dim buffer(bufferSize - 1) AsByteDim extractedFiles As Dictionary(OfString, MemoryStream) = New Dictionary(OfString, MemoryStream)()
' NOTE: The zip file can be any type of stream you use. A network stream, a file stream, etc
' The component will never call Stream.Seek() on the stream and will only read to it.
'
' This example will use the 'zipFileStream' memory stream used in the ZipWriter example.
' Make sure the zip file stream is positioned at the start
zipFileStream.Seek(0, SeekOrigin.Begin)
' Create a local header object that will be reused
Dim localHeader AsNew ZipItemLocalHeader()
' Create the ZipReader object around the stream
Using zipReader AsNew ZipReader(zipFileStream)
' As a convenience, ZipReader offers the option to automatically close the input stream when
' ZipReader is closed. The behavior is disabled by default.
'
' In this example, we will not enable it as we may want to manipulate the zip file
' after it has been read.
'zipReader.AllowInputStreamClosure = true;
' Optional. Provide the default password for encrypted items in the archive
zipReader.EncryptionPassword = password
' Optional. Subscribe to available events
AddHandler zipReader.ByteProgression, AddressOf OnByteProgression
AddHandler zipReader.InvalidPassword, AddressOf OnInvalidPassword
' Here, we supply the local header object we created above.
' ReadItemLocalHeader() will fill the object with current data and return a
' reference to the same object.
'
' ReadItemLocalHeader() can also be called without parameters. Then, it will
' create a new ZipItemLocalHeader object and return it with current data.
' While the reader finds local headers
DoWhile zipReader.ReadItemLocalHeader(localHeader) IsNotNothing' NOTE: 'localHeader.FileName' will contain a leading slash
' If the item isn't a folder entry
If (Not localHeader.IsFolder) Then' NOTE: The destination data can be any type of stream you need. A network stream, a file stream, etc
' The component will never call Stream.Seek() on the stream and will only write to it.
'
' This example will use memory data to keep it simple and on point.
Dim outputStream AsNew MemoryStream()
extractedFiles.Add(localHeader.FileName, outputStream)
' Have the reader read the item data and write it to the stream using our buffer
zipReader.ReadItemData(outputStream, buffer, 0, bufferSize)
EndIfLoopEndUsingEnd SubPrivateSharedSub OnInvalidPassword(ByVal sender AsObject, ByVal e As ZipReaderInvalidPasswordEventArgs)
' TODO: We have access to the current item being unzipped. We can report it's name, etc
Dim currentItem As ZipItemLocalHeader = e.ZipItemLocalHeader
' TODO: We're given the password that failed. We can report it, etc
Dim oldPassword AsString = e.OldPassword
' If 'e.NewPassword' is set to null or an empty string, or 'e.Abort' is set to true,
' a ZipReaderException will be thrown for failure to decrypt the item.
' Since items can't be skipped, the entire unzip process will be canceled.
'
' When the event is triggered, 'e.NewPassword' is set to an empty string and 'e.Abort' is set
' to false.
' TODO: We have to supply a new password.
' If that new password is invalid, the event will be triggered again
e.NewPassword = "Some New Password"' TODO: We can ask the entire unzip operation to be aborted
e.Abort = TrueEnd SubPrivateSharedSub OnByteProgression(ByVal sender AsObject, ByVal e As ZipReaderByteProgressionEventArgs)
' TODO: We have access to the current item being unzipped. We can report it's name, etc
Dim currentItem As ZipItemLocalHeader = e.ZipItemLocalHeader
' TODO: We're given the current amount of bytes unzipped for the item. We can report it, etc
Dim bytesProcessed AsLong = e.BytesProcessed
' Do not assume that e.UncompressedSize and e.Percent will contain useful values.
' Since ZipReader doesn't seek in the archive, it cannot always know the uncompressed
' size in advance.
End Sub