Problem with self extracting stub saved directly to Response.OutputStream

Oct 22, 2010 at 10:42 AM
Edited Oct 25, 2010 at 6:29 AM

Hi

I want to create and download a selfextracting zip file and for this I am using the following code in my asp.net application:

string urlsXmlFileName = "urlsXml.xml";
string urlsXmlFilePath = Path.Combine(tempFolderPath, urlsXmlFileName);

string autoRunExeName = "ImageReplacer.exe";
string extracterStub = Server.MapPath("screensaverExeFiles/" + autoRunExeName);

Response.Clear();
Response.AddHeader("Content-disposition", "attachment; filename=" + "CorbisScreensaverImageReplacer.exe");

Response.AddHeader("Content-Description", "File Transfer");
Response.AddHeader("Content-Transfer-Encoding", "binary");
Response.ContentType = "application/exe";

 using (System.IO.Stream instream = System.IO.File.Open(extracterStub, System.IO.FileMode.Open))
 {
     byte[] buffer = new byte[4000];
     int count = 1;
     while (count != 0)
     {
         count = instream.Read(buffer, 0, buffer.Length);
         if (count != 0)
             Response.OutputStream.Write(buffer, 0, count);  
     }
     instream.Flush();
     instream.Close();
  
    using (ZipFile zip = new ZipFile())
     {
    	XElement urls = new XElement("urls",
                     	from image in imageUrls
                     	select
                       	new XElement("url",
                        new XAttribute("imageId", image.Key), image.Value)
                         );
                                         
   	XDocument xdoc = new XDocument(new XDeclaration("1.0", null, null), urls);
   	xdoc.Save(urlsXmlFilePath);
                    
    	ZipEntry entry = null;                    
    	entry = zip.AddFile(urlsXmlFilePath);
    	entry.FileName = "urlsXml.xml";
    	zip.Save(Response.OutputStream);

    	Response.Flush();
                        
    	Directory.Delete(tempFolderPath, true);
   	HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
On running the downloaded exe created by the above code,I am getting following error: 
 zipEntry:: ReadDirEntry():Bad signature(0xFE160400) at position 0x000006FC

But  things  work properly if I use following code:

string urlsXmlFileName = "urlsXml.xml";
string urlsXmlFilePath = Path.Combine(tempFolderPath, urlsXmlFileName);

string autoRunExeName = "CorbisScreensaverImagesReplacer.exe";
string extracterStub = Server.MapPath("screensaverExeFiles/" + autoRunExeName);

Response.Clear();
Response.AddHeader("Content-disposition", "attachment; filename=" + "CorbisScreensaverImageReplacer.exe");
Response.AddHeader("Content-Description", "File Transfer");
Response.AddHeader("Content-Transfer-Encoding", "binary");
Response.ContentType = "application/exe";

string selfExtractingFilepath = Path.Combine(tempFolderPath, "SetCorbisScreenSaverImages.exe");
using (System.IO.Stream oStream = System.IO.File.Open(selfExtractingFilepath, System.IO.FileMode.OpenOrCreate))
{
	using (System.IO.Stream instream = System.IO.File.Open(extracterStub, System.IO.FileMode.Open))
	{
		byte[] buffer = new byte[4000];
		int count = 1;

		while (count != 0)
		{
			count = instream.Read(buffer, 0, buffer.Length);
			if (count != 0)
			{
				oStream.Write(buffer, 0, count);
			}
		}
			instream.Flush();
			instream.Close();
	}
	using (ZipFile zip = new ZipFile())
	{
		
		XElement urls = new XElement("urls",
                     	from image in imageUrls
                     	select
                       	new XElement("url",
                        new XAttribute("imageId", image.Key), image.Value)
                         );
XDocument xdoc = new XDocument(new XDeclaration("1.0", null, null), urls);
 		xdoc.Save(urlsXmlFilePath);
	       ZipEntry entry = null;                    
    	       entry = zip.AddFile(urlsXmlFilePath);
    	       entry.FileName = "urlsXml.xml";
  		zip.Save(oStream);
 		oStream.Flush();
 		oStream.Close();
  		System.IO.FileStream fs = null;
  		fs = System.IO.File.Open(selfExtractingFilepath, System.IO.FileMode.Open);
 		byte[] btFile = new byte[fs.Length]; 
		fs.Read(btFile, 0, Convert.ToInt32(fs.Length)); 
		fs.Close(); 
 		Response.BinaryWrite(btFile); 
		Response.Flush();
  		Directory.Delete(tempFolderPath, true); 
		HttpContext.Current.ApplicationInstance.CompleteRequest();
	}
}

Actually I don't want to save and read any temporary file. Could anyone explain this behavior(i.e. difference between the exes created by these two different approaches). Please give me  some solution.

Oct 22, 2010 at 11:44 AM

Hi anu,

I'm guessing a bit, but in your second example can you not replace the following:

using (System.IO.Stream oStream = System.IO.File.Open(selfExtractingFilepath, System.IO.FileMode.OpenOrCreate))

with

using (System.IO.Stream oStream = Response.OutputStream)

This should write the zip file directly to the response stream and you could eliminate the need for reading back from the temporary disk file later in your code.

Hope this helps,

Mike

 

Oct 22, 2010 at 12:01 PM

Actually, that might not work either. I *think* the problem is that the internal zip directory contains offsets relative to the <start of the containing stream>, rather than relative to the <start position of the zip data>.

In your first case you're writing a stub header to a stream and then "Save"ing a zip to the same stream, so DotNetZip will include the length of the stub header in the offset calculations. In the second case you're "Save"in to a separate stream and then appending it to the stub header so the offsets inside the zip part are relative to the start of the zip data.

Two ways I can think of to fix it are:

+ Use a self-extracter stub which calculates offset calculations similar to DotNetZip (i.e. from the start of the stream, not the start of the zip data).

+ Do what you're doing in the second example, but write to a memory stream instead so you're not creating a disk file. Not ideal, but it eliminates temporary files.

Also, I've not tried doing this myself, so there might be some other way of doing it that makes a lot more sense.

M

Oct 22, 2010 at 12:26 PM

Hi Pointy,

Thanx for the quick reply. Sorry bt I am still not understanding ur point  "In the second case you're "Save"in to a separate stream and then appending it to the stub header so the offsets inside the zip part are relative to the start of the zip data."

 In second case I am writing stub to an outputstream first and then appending zip data to the same outputStream. the same thing is in first case except outputstream is Response.OutputStream. 

Oct 22, 2010 at 12:36 PM

Hi Pointy , 

One more thing that i want to mention is that in both cases I am able to extract "urlsxml.xml" file using winrar.  In ExtracterStub exe I am using following code to extract my files :

       using (Ionic.Zip.ZipFile zip = Ionic.Zip.ZipFile.Read(assembly.Location))
               {                  
                    string tempFolderPath = Path.Combine(Path.GetTempPath(),"MyTemp");
                    if (!Directory.Exists(tempFolderPath))
                    {
                        Directory.CreateDirectory(tempFolderPath);
                    }
                    zip.ExtractAll(tempFolderPath, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);

                    if (File.Exists(Path.Combine(tempFolderPath, "urlsXml.xml")))
                    {
                        XmlFilePath = Path.Combine(tempFolderPath, "urlsXml.xml");
                    }
		}


 

Oct 22, 2010 at 12:46 PM

Ah, I might have misread your second example. I'll need to spend a bit more time reading it over the weekend.

I'll let you know if I find anything.

M

Oct 22, 2010 at 3:55 PM

Could you double-check the second example in your original post above?

I can't seem to see where it actually writes your xml file out to the zip. I can see a call to zip.Save(oStream), but the xml file doesn't seem to get added to the zip beforehand.

Coordinator
Oct 23, 2010 at 10:49 PM

Also, Anu12345, all the most interesting stuff in the sample code in your original post here, was contained on a single line.  There's the creation of an XML file, saving the zip file, a bunch of IO, and some other stuff.

Would it be possible for you to edit that post and reformat the code so that it is more easily readable?

 

Oct 25, 2010 at 7:20 AM

Hi 

Sorry for inconvenience. I hv edited my post. Please do let me know if there is any solution of this problem.

Coordinator
Oct 27, 2010 at 2:05 AM

Super. Much better.

Ah, ok, I think I understand better now.

The reason why the first version is not giving you happy results is that the offsets in your self-extracting zip are ...off.

Here's the thing.  A zip file contains a directory, somewhere in the file, which lists the position of all the zip entries in the file.  These positions are offsets from the beginning of the file.  An SFX is also a ZIP; it just happens to have a PE-COFF (Executable) file before all the zip content.  This is no problem at all, for reading the sfx file as a regular zip file, as long the offsets are correct.   Suppose you open a filesystem file (getting a FileStream), save the sfx stub into it, and then call ZipFile.Save() into that FileStream.  ZipFile.Save() does the right thing for the zipentry offsets, by inquiring the Position of the FileStream before writing any data, and then adding that initial offset into any ZipEntry offsets in the zip directory.  Does this make sense?  

This does not work for HttpResponse.OutputStream, because it is not possible to inquire the Position on that stream.  Position is not defined for that stream type.  Therefore the writes that happen within the context of ZipFile.Save() cannot do the fixup arithmetic I described just above.

What can you do?

One way to do it is to wrap HttpResponse.OutputStream in a wrapper stream that supports the Position property; then write the SFX stub and the ZipFile into that wrapper stream. Because ZipFile.Save() can inquire the Position, it will then do the right thing with the offsets.   In the source for DotNetZip, there is a wrapper stream class called CountingStream that does exactly what you want;  unfortunately it is limited to internal use. You'll have to add this code to your project to use it.

I think it's a reasonable change request to expose CountingStream as a public class, to make what you're doing much easier.  I';ll open a workitem for that.

Good luck!

 

 

Coordinator
Oct 27, 2010 at 2:07 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.