Search code examples
c#dotnetzip

DotNetZip save self-extractor directly to Response.OutputStream


I'm creating a REST endpoint which should return a sfx (Self Extracting Archive). I'm using Ionic.Zip to do the heavy lifting as far as creating the actual archive, but I'm having some trouble understanding how I should write the finished sfx archive back to the client.

From what I can tell, ZipFile.Save(Response.OutputStream) works fine in order to write a zip file back, and I was rather surprised that I could not do the same using something like ZipFile.SaveSelfExtractor(Response.OutputStream, options). According to the docs, there is no overload for SaveSelfExtractor which takes in a stream.

The examples I'm able to dig up on the web explains either

  1. how I can create my own stub and write that back to the stream first, and then write the zip archive on top of the same stream.
  2. how I can temporarily store the sfx on the server, and then write it back to the client using a FileStream.

But I do not need nor want to temporarily store the sfx executable on the server, and I do not want to create my own sfx stub. I'm quite happy to use the stub already provided in the Ionic package.

Is there any way for me to just let the Ionic.Zip.ZipFile create the sfx and write it back to Response.OutputStream in one go?

This is what I have now:

using System.IO;
using Ionic.Zip;
using System.Web.Http;
using Context = System.Web.HttpContext;

namespace MyCompany.web.Controllers
{
    [HttpGet]
    public void Export()
    {
        var response = Context.Current.Response;
        var stream = response.OutputStream;

        // Create the zip archive in memory
        using (var archive = new ZipFile())
        {
            archive.Comment = "Self extracting export";
            archive.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;

            using (var memoryStream = new MemoryStream())
            using (var streamWriter = new StreamWriter(memoryStream))
            {
                streamWriter.WriteLine("Hello World");
                archive.AddEntry("testfile.txt", memoryStream.ToArray());
            }

            // What I want is to write this to outputstream
            archive.SaveSelfExtractor(/*stream*/ "export.exe", new SelfExtractorSaveOptions
            {
                Flavor = SelfExtractorFlavor.ConsoleApplication,
                Quiet = true,
                ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently,
                RemoveUnpackedFilesAfterExecute = false
            });
            /*archive.Save(stream); // This will write to outputstream */
        }

        response.AddHeader("Content-Disposition", "attachment; filename=export.exe");
        response.AddHeader("Content-Description", "File Transfer");
        response.AddHeader("Content-Transfer-Encoding", "binary");
        response.ContentType = "application/exe";

        response.Flush();
        response.Close();
        response.End();
    }
}

Solution

  • I'm posting this for posterity. It was the best solution I could come up with. Others are welcome to provide a better solution.

        [HttpGet]
        public void Export()
        {
            var response = Context.Current.Response;
            var writeStream = response.OutputStream;
    
            var name      = "export.exe";
    
            // Create the zip archive in memory
            using (var archive = new Ionic.Zip.ZipFile())
            {
                archive.Comment = "Self extracting export";
                archive.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
    
                using (var memoryStream = new MemoryStream())
                using (var streamWriter = new StreamWriter(memoryStream))
                {
                    streamWriter.WriteLine("Hello World");
                    streamWriter.Flush();
                    archive.AddEntry("testfile.txt", memoryStream.ToArray());
                }
    
                // Write sfx file to temp folder on server
                archive.SaveSelfExtractor(name, new Ionic.Zip.SelfExtractorSaveOptions
                {
                    Flavor = Ionic.Zip.SelfExtractorFlavor.ConsoleApplication,
                    Quiet = true,
                    DefaultExtractDirectory = "\\temp",
                    SfxExeWindowTitle = "Export",
                    ExtractExistingFile = Ionic.Zip.ExtractExistingFileAction.OverwriteSilently,
                    RemoveUnpackedFilesAfterExecute = false
                });
    
                // Read file back and output to response
                using (var fileStream = new FileStream(name, FileMode.Open))
                {
                    byte[] buffer = new byte[4000];
                    int n = 1;
                    while (n != 0)
                    {
                        n = fileStream.Read(buffer, 0, buffer.Length);
                        if (n != 0)
                            writeStream.Write(buffer, 0, n);
                    }
                }
    
                // Delete the temporary file
                if (File.Exists(name))
                {
                    try { File.Delete(name); }
                    catch (System.IO.IOException exc1)
                    {
                        Debug.WriteLine("Warning: Could not delete file: {0} {1}", name, exc1);
                    }
                }
            }
    
            response.AddHeader("Content-Disposition", "attachment; filename=" + name);
            response.AddHeader("Content-Description", "File Transfer");
            response.AddHeader("Content-Transfer-Encoding", "binary");
            response.ContentType = "application/exe";
    
            response.Flush();
            response.Close();
            response.End();
        }