使用 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);
我正在使用 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);