Reputation: 427
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
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:
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