Xceed Real-Time Zip for .NET Documentation
Reading and writing nested Zip archives

Welcome to Xceed Real-Time Zip for .NET, .NET Standard & Xamarin > Task-Based Help > Reading and writing nested Zip archives

The following examples show how to use the ZipReader.GetItemDataStream and ZipWriter.GetItemDataStream methods to allow the component to easily handle nested zip archives.

A nested zip archive is when an item in an archive is another zip archive. By providing the stream returned by the method to a new instance of ZipWriter or ZipReader, a nested zip file will be created or read.

The first example shows how the ZipWriter.GetItemDataStream() method can be used to create a nested Zip archive.

static void NestedArchiveExample( Stream someWriteStream )
{
  string password = "password";

  // Create the ZipWriter object around the supplied stream
  using( ZipWriter writer = new ZipWriter( someWriteStream ) )
  {
    /* OPTIONAL, BUT USEFUL: Allow uncompressed items in the archive. This will allow nested
     * zip files to be accessed and read without first having to unzip the entire rested zip file
     * from the 'outer' archive. */
    
    // Enable the use of uncompressed data in the archive
    writer.AllowUncompressedData = true;

    /* Create a local header object that will be used to specify the path/filename in
       the zip file and other options like compression and encryption. */
    ZipItemLocalHeader header = new ZipItemLocalHeader();

    // Setup a normal file for zipping
    header.FileName = "File1.dat";
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated;
    header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal;
    header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes;
    header.EncryptionPassword = password;
    
    // Write the local header for the item
    writer.WriteItemLocalHeader( header );
    
    // Open the file's data
    using( Stream dataStream = new FileStream( @"C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read ) )
    {
      // Zip the data into the archive
      writer.WriteItemData( dataStream );
    }

    /* Now, we will add a nested zip file in the archive. For easy unzipping later,
     * the best approach is to add the nested zip file without compression or encryption.
     * 
     * This way, when unzipping, the nested zip file can be accessed directly with seeking
     * enabled.
     * 
     * If compression or encryption is enabled, the nested zip file will have to be
     * unzipped from the outer zip file into a temporary location before it can be itself unzipped.
     * 
     * The nested zip file itself can, of course, use compression and encryption for its items
     * without issue. So there is no loss of size and security with this approach.
     */

    // Assign a filename for the nested zip archive
    header.FileName = "File1.zip";

    // Disable compression and encryption for the nested zip archive
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Stored;
    header.EncryptionPassword = String.Empty;

    // Write the local header for the file in the archive
    writer.WriteItemLocalHeader( header );

    // Create a write stream that wraps the item's data
    using( Stream itemStream = writer.GetItemDataStream() )
    {
      // Feed the item stream to a new instance of ZipWriter
      using( ZipWriter nestedWriter = new ZipWriter( itemStream ) )
      {
        /* The 'using' statement will insure the nested zip file is properly terminated */

        // Add an item and some data into the nested zip file
        header.FileName = @"\SomeFolder\File1.dat";
        header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated;
        header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal;
        header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes;
        header.EncryptionPassword = password;
        nestedWriter.WriteItemLocalHeader( header );

        // Open the file's data
        using( Stream dataStream = new FileStream( @"C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read ) )
        {
          // Zip the data into the archive
          nestedWriter.WriteItemData( dataStream );
        }

        /* TODO: More items can be added to the nested archive this way... */
      }
    }

    // Setup a normal file for zipping
    header.FileName = "File34.dat";
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated;
    header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal;
    header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes;
    header.EncryptionPassword = password;

    // Write the local header for the item
    writer.WriteItemLocalHeader( header );

    // Open the file's data
    using( Stream dataStream = new FileStream( @"C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read ) )
    {
      // Zip the data into the archive
      writer.WriteItemData( dataStream );
    }
  }
}
Private Shared Sub NestedArchiveExample(ByVal someWriteStream As Stream)
  Dim password As String = "password"

  ' Create the ZipWriter object around the supplied stream
  Using writer As New ZipWriter(someWriteStream)
    '         OPTIONAL, BUT USEFUL: Allow uncompressed items in the archive. This will allow nested
    '         * zip files to be accessed and read without first having to unzip the entire rested zip file
    '         * from the 'outer' archive. 

    ' Enable the use of uncompressed data in the archive
    writer.AllowUncompressedData = True

    '         Create a local header object that will be used to specify the path/filename in
    '           the zip file and other options like compression and encryption. 
    Dim header As New ZipItemLocalHeader()

    ' Setup a normal file for zipping
    header.FileName = "File1.dat"
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated
    header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal
    header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes
    header.EncryptionPassword = password

    ' Write the local header for the item
    writer.WriteItemLocalHeader(header)

    ' Open the file's data
    Using dataStream As Stream = New FileStream("C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read)
      ' Zip the data into the archive
      writer.WriteItemData(dataStream)
    End Using

    '         Now, we will add a nested zip file in the archive. For easy unzipping later,
    '         * the best approach is to add the nested zip file without compression or encryption.
    '         * 
    '         * This way, when unzipping, the nested zip file can be accessed directly with seeking
    '         * enabled.
    '         * 
    '         * If compression or encryption is enabled, the nested zip file will have to be
    '         * unzipped from the outer zip file into a temporary location before it can be itself unzipped.
    '         * 
    '         * The nested zip file itself can, of course, use compression and encryption for its items
    '         * without issue. So there is no loss of size and security with this approach.
    '         

    ' Assign a filename for the nested zip archive
    header.FileName = "File1.zip"

    ' Disable compression and encryption for the nested zip archive
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Stored
    header.EncryptionPassword = String.Empty

    ' Write the local header for the file in the archive
    writer.WriteItemLocalHeader(header)

    ' Create a write stream that wraps the item's data
    Using itemStream As Stream = writer.GetItemDataStream()
      ' Feed the item stream to a new instance of ZipWriter
      Using nestedWriter As New ZipWriter(itemStream)
        ' The 'using' statement will insure the nested zip file is properly terminated 

        ' Add an item and some data into the nested zip file
        header.FileName = "\SomeFolder\File1.dat"
        header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated
        header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal
        header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes
        header.EncryptionPassword = password
        nestedWriter.WriteItemLocalHeader(header)

        ' Open the file's data
        Using dataStream As Stream = New FileStream("C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read)
          ' Zip the data into the archive
          nestedWriter.WriteItemData(dataStream)
        End Using

        ' TODO: More items can be added to the nested archive this way... 
      End Using
    End Using

    ' Setup a normal file for zipping
    header.FileName = "File34.dat"
    header.CompressionMethod = Xceed.Compression.CompressionMethod.Deflated
    header.CompressionLevel = Xceed.Compression.CompressionLevel.Normal
    header.EncryptionMethod = Xceed.Zip.EncryptionMethod.WinZipAes
    header.EncryptionPassword = password

    ' Write the local header for the item
    writer.WriteItemLocalHeader(header)

    ' Open the file's data
    Using dataStream As Stream = New FileStream("C:\Windows\Notepad.exe", FileMode.Open, FileAccess.Read)
      ' Zip the data into the archive
      writer.WriteItemData(dataStream)
    End Using
  End Using
End Sub

The second example shows how the ZipReader.GetItemDataStream method can be used to read a nested Zip archive.

private void ReadZipArchive( Stream archiveStream )
{
  // Create the ZipReader object around the stream
  using( ZipReader reader = new ZipReader( archiveStream ) )
  {
    ZipItemLocalHeader header;

    // While the reader finds local headers
    while( ( header = reader.ReadItemLocalHeader() ) != null )
    {
      /* The component will not automatically identity what is and is
       * not a nested zip file. The application needs to have its own mechanism.
       *
       * This example will keep it simple and just look at the item's file name extension. */

      // If the item's extension is .zip
      if( StringComparer.OrdinalIgnoreCase.Compare( Path.GetExtension( header.FileName ), ".zip" ) == 0 )
      {
        // Create a read stream that wraps the item's data
        using( Stream itemStream = reader.GetItemDataStream() )
        {
          /* NOTE: While using a recursive call here makes for elegant and compact code that
           * helps illustrate the concept of nested zip archives, a maliciously crafted zip file
           * made up of a large number of nested zip files could make this code cause a stack overflow
           * due to excessive recursion. */

          // Call ourselves to read the nested archive
          this.ReadZipArchive( itemStream );
        }

        /* IMPORTANT: Before we can move on to the next item in the archive, ZipReader must have
         * reached the end of the current item's data. Reading a nested zip file does not ensure
         * this, so we need to make sure here by reading any remaining data into a dummy stream.
         *
         * Failure to do this will result in a ZipReaderException that reports that the object
         * is not in the correct state to read the next item header. */

        // Make sure we reach the end of the item's data
        reader.ReadItemData( Stream.Null );
      }
      else
      {
        // This example does not concern itself with normal items
        reader.ReadItemData( Stream.Null );
      }
    }
  }
}
    Private Sub ReadZipArchive(ByVal archiveStream As Stream)
      ' Create the ZipReader object around the stream
      Using reader As New ZipReader(archiveStream)
        Dim header As ZipItemLocalHeader

        ' While the reader finds local headers
        header = reader.ReadItemLocalHeader()
        Do While header IsNot Nothing
'           The component will not automatically identity what is and is
'           * not a nested zip file. The application needs to have its own mechanism.
'           *
'           * This example will keep it simple and just look at the item's file name extension. 

          ' If the item's extension is .zip
          If StringComparer.OrdinalIgnoreCase.Compare(Path.GetExtension(header.FileName), ".zip") = 0 Then
            ' Create a read stream that wraps the item's data
            Using itemStream As Stream = reader.GetItemDataStream()
'               NOTE: While using a recursive call here makes for elegant and compact code that
'               * helps illustrate the concept of nested zip archives, a maliciously crafted zip file
'               * made up of a large number of nested zip files could make this code cause a stack overflow
'               * due to excessive recursion. 

              ' Call ourselves to read the nested archive
              Me.ReadZipArchive(itemStream)
            End Using

'             IMPORTANT: Before we can move on to the next item in the archive, ZipReader must have
'             * reached the end of the current item's data. Reading a nested zip file does not ensure
'             * this, so we need to make sure here by reading any remaining data into a dummy stream.
'             *
'             * Failure to do this will result in a ZipReaderException that reports that the object
'             * is not in the correct state to read the next item header. 

            ' Make sure we reach the end of the item's data
            reader.ReadItemData(Stream.Null)
          Else
            ' This example does not concern itself with normal items
            reader.ReadItemData(Stream.Null)
          End If
          header = reader.ReadItemLocalHeader()
        Loop
      End Using
    End Sub