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

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

我正在使用 ICSharpCode.SharpZipLib v 0.86.0.518.

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

生成的 Zip 打开以供浏览,但在尝试提取任何内容时,我收到一条错误消息 "The Archive is either in an unknown format or damaged"。

在下面的代码中,当 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: 
        // 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();
                }
            }
        }
    }
}

目前,您过度阅读了您的信息流。 GetBuffer() returns 超大 后备缓冲区;您通常应该将自己限制在缓冲区的前 zipStream.Length 个字节。

所以首先要尝试的是:

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

但是,如果可行,您也可以通过简单地发送 zipStream 来实现相同的目的,只要您:

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

您可能还想知道 .NET 框架本身就支持 zip,不需要额外的工具。

顺便说一句,您的复制代码效率低下(尤其是在不断重新创建缓冲区时),可能只需要使用:

stream.CopyTo(contextResponse.OutputStream);