54

I have a memory stream that contains a zip file in byte[] format. Is there any way I can unzip this memory stream, without any need of writing the file to disk?

In general I am using ICSharpCode.SharpZipLib.Zip.FastZip to unzip a file, but is there any way to unzip a memory stream, maybe by storing the files in another MemoryStream or in byte[] format according to the files/folders present in the zip?

Any way I can use the Memorymapped files feature in this scenario ?

SharpC
  • 6,974
  • 4
  • 45
  • 40
D J
  • 963
  • 2
  • 14
  • 22

4 Answers4

134

Yes, .Net 4.5 now supports more Zip functionality.

Here is a code example based on your description.

In your project, right click on the References folder and add a reference to System.IO.Compression

using System.IO.Compression;

Stream data = new MemoryStream(); // The original data
Stream unzippedEntryStream; // Unzipped data from a file in the archive

ZipArchive archive = new ZipArchive(data);
foreach (ZipArchiveEntry entry in archive.Entries)
{
    if(entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
    {
         unzippedEntryStream = entry.Open(); // .Open will return a stream
         // Process entry data here
    }
}
starball
  • 20,030
  • 7
  • 43
  • 238
gigaduck
  • 1,537
  • 2
  • 9
  • 9
  • Thanks for the input that sounds great ! I would complete with : .... unzippedEntryStream.CopyTo(data); } } return data; – Titwan Dec 19 '17 at 17:31
  • Thanks for the input that sounds great ! I would complete with : .... ZipArchive archive = new ZipArchive(data); if (archive!=null && archive.Entries!=null) { var archlist = archive.Entries.ToList(); ZipArchiveEntry entry = archlist.Find(f => f.Name.Contains(hrdwFileName)); if (entry!=null) { unzippedEntryStream = entry.Open(); unzippedEntryStream.CopyTo(data); unzippedEntryStream = null; } } archive.Dispose(); return data; } – Titwan Dec 19 '17 at 17:39
  • All fun an games until you find out if the input stream is not seekable, it copies the whole file into memory... – mBrice1024 Oct 19 '18 at 17:56
23

We use DotNetZip, and I can unzip the contents of a zip file from a Stream into memory. Here's the sample code for extracting a specifically named file from a stream (LocalCatalogZip) and returning a stream to read that file, but it'd be easy to expand on it.

private static MemoryStream UnZipCatalog()
{
    MemoryStream data = new MemoryStream();
    using (ZipFile zip = ZipFile.Read(LocalCatalogZip))
    {
        zip["ListingExport.txt"].Extract(data);
    }
    data.Seek(0, SeekOrigin.Begin);
    return data;
}

It's not the library you're using now, but if you can change, you can get that functionality.


Here's a variation which would return a Dictionary<string,MemoryStream> of for the contents of every file of a zip file.

private static Dictionary<string,MemoryStream> UnZipToMemory()
{
    var result = new Dictionary<string,MemoryStream>();
    using (ZipFile zip = ZipFile.Read(LocalCatalogZip))
    {
        foreach (ZipEntry e in zip)
        {
            MemoryStream data = new MemoryStream();
            e.Extract(data);
            result.Add(e.FileName, data);
        }
    }

    return result;
}
rgahan
  • 667
  • 8
  • 17
Bobson
  • 13,498
  • 5
  • 55
  • 80
  • 1
    Can you explain "zip["ListingExport.txt"].Extract(data);" ? – D J Oct 04 '12 at 02:02
  • @user1621791 - It looks for the file named "ListingExport.txt" in the zip, then stuffs that file into the `MemoryStream` named `data`. As I said, this example was for a file whose name we knew in advance, but you can see [here](http://dotnetzip.codeplex.com/wikipage?title=CS-Examples&referringTitle=Examples) for examples of iterating over all files. I'll copy that into my answers. – Bobson Oct 04 '12 at 14:18
  • 1
    What is LocalCatalogZip ? Is it a memory stream of the zip file? – Rajesh Dec 13 '12 at 15:04
  • @Rajesh - I believe it can be either a memory stream or a path to a file. Possibly even a `FileInfo`. There's a lot of overloads for `.Read()`. – Bobson Dec 13 '12 at 16:00
16

I've just had a similar issue and the answer I found which I think seems to be fairly elegant is to use #ZipLib (available using nuget) and do the following:

private byte[] GetUncompressedPayload(byte[] data)
{
    using (var outputStream = new MemoryStream())
    using (var inputStream = new MemoryStream(data))
    {
        using (var zipInputStream = new ZipInputStream(inputStream))
        {
            zipInputStream.GetNextEntry();
            zipInputStream.CopyTo(outputStream);
        }
        return outputStream.ToArray();
    }
}

This seems to have worked a treat. Hope this helps.

kipper_t
  • 387
  • 3
  • 12
9

Yes, Change from using FastZip To new ZipFile(stream), but this only works if your stream can seek. (Just use your MemoryStream in new ZipFile(fs); instead of reading a file stream like the example.)

C#
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;

public void ExtractZipFile(string archiveFilenameIn, string password, string outFolder) {
    ZipFile zf = null;
    try {
        FileStream fs = File.OpenRead(archiveFilenameIn);
        zf = new ZipFile(fs);
        if (!String.IsNullOrEmpty(password)) {
            zf.Password = password;     // AES encrypted entries are handled automatically
        }
        foreach (ZipEntry zipEntry in zf) {
            if (!zipEntry.IsFile) {
                continue;           // Ignore directories
            }
            String entryFileName = zipEntry.Name;
            // to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
            // Optionally match entrynames against a selection list here to skip as desired.
            // The unpacked length is available in the zipEntry.Size property.

            byte[] buffer = new byte[4096];     // 4K is optimum
            Stream zipStream = zf.GetInputStream(zipEntry);

            // Manipulate the output filename here as desired.
            String fullZipToPath = Path.Combine(outFolder, entryFileName);
            string directoryName = Path.GetDirectoryName(fullZipToPath);
            if (directoryName.Length > 0)
                Directory.CreateDirectory(directoryName);

            // Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
            // of the file, but does not waste memory.
            // The "using" will close the stream even if an exception occurs.
            using (FileStream streamWriter = File.Create(fullZipToPath)) {
                StreamUtils.Copy(zipStream, streamWriter, buffer);
            }
        }
    } finally {
        if (zf != null) {
            zf.IsStreamOwner = true; // Makes close also shut the underlying stream
            zf.Close(); // Ensure we release resources
        }
    }
}

If you are using a non-seekable stream use ZipInputStream.

// Calling example:
    WebClient webClient = new WebClient();
    Stream data = webClient.OpenRead("http://www.example.com/test.zip");
    // This stream cannot be opened with the ZipFile class because CanSeek is false.
    UnzipFromStream(data, @"c:\temp");

public void UnzipFromStream(Stream zipStream, string outFolder) {

    ZipInputStream zipInputStream = new ZipInputStream(zipStream);
    ZipEntry zipEntry = zipInputStream.GetNextEntry();
    while (zipEntry != null) {
        String entryFileName = zipEntry.Name;
        // to remove the folder from the entry:- entryFileName = Path.GetFileName(entryFileName);
        // Optionally match entrynames against a selection list here to skip as desired.
        // The unpacked length is available in the zipEntry.Size property.

        byte[] buffer = new byte[4096];     // 4K is optimum

        // Manipulate the output filename here as desired.
        String fullZipToPath = Path.Combine(outFolder, entryFileName);
        string directoryName = Path.GetDirectoryName(fullZipToPath);
        if (directoryName.Length > 0)
            Directory.CreateDirectory(directoryName);

        // Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
        // of the file, but does not waste memory.
        // The "using" will close the stream even if an exception occurs.
        using (FileStream streamWriter = File.Create(fullZipToPath)) {
            StreamUtils.Copy(zipInputStream, streamWriter, buffer);
        }
        zipEntry = zipInputStream.GetNextEntry();
    }
}

Examples taken from the ICSharpCode Wiki

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431