使用SharpZipLib在内存中创建Zip会导致存档损坏

时间:2015-03-19 14:27:41

标签: c# .net memorystream sharpziplib

我正在使用ICSharpCode.SharpZipLib v 0.86.0.518。

我有一个流,其内容我想作为文件添加到Zip,它也必须在内存中创建(而不是在磁盘上)。

生成的Zip会打开以供浏览,但在尝试提取任何内容时,我收到一条错误消息,指出“存档处于未知格式或已损坏”。

在下面的代码中,asZip=false发送文本文件,并按预期收到。当asZip=true时,文件被发送,但遭受上述损坏。

当我用MemoryStream zipStream替换FileStream zipStream时,磁盘上的文件就可以了。 谁能看到我错过的东西?

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();
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:3)

目前,您正在阅读您的信息流。 GetBuffer()返回超大支持缓冲区;你通常应该将自己限制在缓冲区的第一个zipStream.Length字节。

所以首先要尝试的是:

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

但是,如果可以,只要您发送zipStream,您也可以实现相同的目标:

  • 写完
  • 后回流
  • 确保它不会被using
  • 关闭

您可能也有兴趣听说.NET框架内部存在zip支持,而无需其他工具。

您的复制代码(btw)效率低下(特别是在不断重新创建缓冲区时)并且可能只是使用:

stream.CopyTo(contextResponse.OutputStream);