GoetzOnline
GoetzOnline

Reputation: 427

Creating a Zip in memory with SharpZipLib resulting in a corrupt archive

I am using ICSharpCode.SharpZipLib v 0.86.0.518.

I have a a stream whose contents I would like to add as a file to a Zip, which must also be created in memory (as opposed to on on disk).

The resulting Zip opens for browsing, but when attempting to extract any contents, I get an error stating "The Archive is either in an unknown format or damaged".

In the code below, when asZip=false the text file is sent, and received as expected. When asZip=true, the file is sent, but suffers the corruption described above.

When I replace MemoryStream zipStream, with FileStream zipStream, the file on disk is OK. Can anyone see what I've missed?

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net; // .NET 4.0
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Mvc;
using ICSharpCode.SharpZipLib.Zip;//0.86.0.518

namespace Demo
{
    public class DemoApiController : Controller
    {
        /// <summary>
        /// demos the zipfile error
        /// </summary>
        /// <param name="withFiles">Creates the zip if set to <c>true</c> [with files].</param>
        [HttpGet]
        public void ZipErrorDemo(bool asZip)
        {
            const string fileContent = "Hello World!";

            MemoryStream rawContentStream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(fileContent));
            if (!asZip)
            {
                //This File is recieved as text, opens without erros and has correct content.
                WriteStreamToDownload(rawContentStream, "text/plain", "HelloWorld.txt");
            }
            else
            {
                MemoryStream zipStream = new MemoryStream(1024 * 2048);//2MB

                using (ZipOutputStream s = new ZipOutputStream(zipStream))
                {
                    s.UseZip64 = ICSharpCode.SharpZipLib.Zip.UseZip64.Off; //No Zip64 for better compatability
                    s.SetLevel(0); //No compression
                    byte[] buffer = new byte[4096];

                    //Add the text file
                    ZipEntry csvEntry = new ZipEntry("HelloWorld.txt");
                    s.PutNextEntry(csvEntry);
                    int sourceBytes = 0;
                    do
                    {
                        sourceBytes = rawContentStream.Read(buffer, 0, buffer.Length);
                        s.Write(buffer, 0, sourceBytes);
                    } while (sourceBytes > 0);
                    s.CloseEntry();

                    s.IsStreamOwner = false;//Tells s.Close to not mess invoke zipStream.Close()
                    s.Flush();
                    s.Finish();
                    s.Close();

                    byte[] streamBuffer = zipStream.GetBuffer();//Before doing this things were worse.
                    MemoryStream newStream = new MemoryStream(streamBuffer);

                    //This File is recieved as a zip, opens to list contents, but atemtps at extraction result in an error.
                    WriteStreamToDownload(newStream, "application/zip", "HelloWorld.zip");
                }

            }
        }


        // Adapted from: http://stackoverflow.com/questions/5596747/download-stream-file-from-url-asp-net
        // Accessed: 3/17/15.  Works. 
        private static void WriteStreamToDownload(Stream stream, string contentType, string fileName)
        {
            // 100 kb
            const int bytesToRead = 102400;

            byte[] buffer = new byte[bytesToRead];
            var contextResponse = System.Web.HttpContext.Current.Response;
            try
            {
                contextResponse.ContentType = contentType;
                contextResponse.AddHeader("Content-Disposition", "attachment; filename=\"" + Path.GetFileName(fileName) + "\"");
                contextResponse.AddHeader("Content-Length", stream.Length.ToString());

                int length;

                do
                {
                    if (contextResponse.IsClientConnected)
                    {
                        length = stream.Read(buffer, 0, bytesToRead);
                        contextResponse.OutputStream.Write(buffer, 0, length);
                        contextResponse.Flush();
                        buffer = new byte[bytesToRead];
                    }
                    else
                    {
                        length = -1;
                    }
                } while (length > 0);
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                    stream.Dispose();
                }
            }
        }
    }
}

Upvotes: 0

Views: 3237

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062745

At the moment, you are over-reading your stream. GetBuffer() returns the oversized backing buffer; you should usually limit yourself to the first zipStream.Length bytes of the buffer.

So the first thing to try is:

MemoryStream newStream = new MemoryStream(streamBuffer, 0
    (int)zipStream.Length);

However, if that works, you can probably also achieve the same thing by simply sending zipStream, as long as you:

  • rewind the stream after writing
  • ensure that it doesn't get closed by the using

You might also be interested to hear that zip support is present inside the .NET framework itself, without requiring additional tools.

Your copying code, btw, is inefficient (especially when the buffer is constantly recreated) and could probably just use:

stream.CopyTo(contextResponse.OutputStream);

Upvotes: 3

Related Questions