Create ZipArchive from XML with base64 encoded content - c#

I am creating an XML file on the fly.
One of it's nodes contains a ZIP file encoded as a BASE64 string.
I then create another ZIP file.
I add this XML file and a few other JPEG files.
I output the file to the browser.
I am unable to open the FINAL ZIP file.
I get: "Windows cannot open the folder. The Compressed(zipped) Folder'c:\path\file.zip' is invalid."
I am able to save my original XML file to the file system.
I can open that XML file, decode the ZIP node and save to the file system.
I am then able to open that Zip file with no problems.
I can create the final ZIP file, OMIT my XML file, and the ZIP file opens no problem.
I seem to only have an issue with I attempt to ZIP an XML file that has a node with ZIP content encoded as a BASE64 string.
Any ideas? Code snipets are below. Heavily edited.
XDocument xDoc = new XDocument();
XDocument xDocReport = new XDocument();
XElement xNodeReport;
using (FileStream fsData = new FileStream(strFullFilePath, FileMode.Open, FileAccess.Read)) {
xDoc = XDocument.Load(fsData);
xNodeReport = xDoc.Element("Data").Element("Reports").Element("Report");
//SNIP
//create XDocument xDocReport
//SNIO
using (MemoryStream zipInMemoryReport = new MemoryStream()) {
using (ZipArchive zipFile = new ZipArchive(zipInMemoryReport, ZipArchiveMode.Update)) {
//Add REPORT to ZIP file
ZipArchiveEntry entryReport = zipFile.CreateEntry("data.xml");
using (StreamWriter writer = new StreamWriter(entryReport.Open())) {
writer.Write(xDocReport.ToString());
} //END USING report entry
}
xNodeReport.Value = System.Convert.ToBase64String(zipInMemoryReport.GetBuffer());
//I am able to write this file to disk and manipulate it no problem.
//File.WriteAllText("c:\\users\\snip\\desktop\\Report.xml",xDoc.ToString());
}
//create ZIP for response
using (MemoryStream zipInMemory = new MemoryStream()) {
using (ZipArchive zipFile = new ZipArchive(zipInMemory, ZipArchiveMode.Update)) {
//Add REPORT to ZIP file
ZipArchiveEntry entryReportWrapper = zipFile.CreateEntry("Report.xml");
//THIS IS THE STEP THAT makes the Zip "invalid". Although i can open and manipulate this source file no problem.
//********
using (StreamWriter writer = new StreamWriter(entryReportWrapper.Open())) {
xDoc.Save(writer);
}
//Add JPEG(s) to report
//Create Charts
if (chkDLSalesPrice.Checked) {chartDownloadSP.SaveImage(entryChartSP.Open(), ChartImageFormat.Jpeg);}
if (chkDLSalesDOM.Checked) {chartDownloadDOM.SaveImage(entryChartDOM.Open(), ChartImageFormat.Jpeg);}
if (chkDLSPLP.Checked) {chartDownloadSPLP.SaveImage(entryChartSPLP.Open(), ChartImageFormat.Jpeg);}
if (chkDLSPLP.Checked) {chartDownloadLP.SaveImage(entryChartLP.Open(), ChartImageFormat.Jpeg);}
} // END USING ziparchive
Response.Clear();
Response.AppendHeader("content-disposition", "attachment; filename=file.zip");
Response.ContentType = "application/zip";
Response.BinaryWrite(zipInMemory.GetBuffer());
Response.End();

Without a good, minimal, complete code example, it's impossible to know for sure what bugs are in the code. But there are at least two apparent errors in the code snippet you posted, one of which could easily be responsible for the "invalid .zip" error:
In the statement writer.Write(xDocReport.ToString());, the variable xDocReport has not been initialized to anything useful, at least not in the code you posted. So you'll get an empty XML document in the archive.
Since the code example is incomplete, it's possible you just omitted from the code example in your question the initialization of that variable to something else. In any case, even if you didn't that would just lead to an empty XML document in the archive, not an invalid archive.
More problematic though…
You are calling GetBuffer() on your MemoryStream objects, instead of ToArray(). You want the latter. The former gets the entire backing buffer for the MemoryStream object, including the uninitialized bytes past the end of the valid stream. Since a valid .zip file includes a CRC value at the end of the file, adding extra data beyond that causes anything trying to read the file as a .zip archive to miss the correct CRC, reading the uninitialized data instead.
Replace your calls to GetBuffer() with calls to ToArray() instead.
If the above does not lead to a solution for your problem, you should edit your post, to provide a better code example.
One last comment: there is no point in initializing a variable like xDoc to an empty XDocument object when you're going to just replace that object with a different one (e.g. by calling XDocument.Load()).

Related

How to read data from inner archives without extracting zip file?

I have a zip file which contains inner zip file (Ex:ZipFile1.zip->ZipFile2.zip->file.txt). I want to read the data of inner archive file content (file.txt) using ICSharpCode.SharpZipLib library without extracting to disk. Is it possible? If it is possible, Let me know how to get this.
Based on this answer, you can open a file within the zip as a Stream. You can also open a ZipFile from a Stream. I'm sure you can see where this is heading.
using (var zip = new ZipFile("ZipFile1.zip"))
{
var nestedZipEntry = zip.GetEntry("ZipFile2.zip");
using (var nestedZipStream = zip.GetInputStream(nestedZipEntry))
using (var nestedZip = new ZipFile(nestedZipStream))
{
var fileEntry = nestedZip.GetEntry("file.txt");
using (var fileStream = nestedZip.GetInputStream(fileEntry))
using (var reader = new StreamReader(fileStream))
{
Console.WriteLine(reader.ReadToEnd());
}
}
}
What we're doing here:
Open ZipFile1.zip
Find the entry for ZipFile2.zip
Open ZipFile2.zip as a Stream
Create a new ZipFile object around nestedZipStream.
Find the entry for file.txt
Create a StreamReader around fileStream to read the text file.
Read the contents of file.txt and output it to the console.
Try it online - in this sample, the base64 data is the binary data of a zip file which contains "test.zip", which in turn contains "file.txt". The contents of that text file is "hello".
P.S. If an entry isn't found then GetEntry will return null. You'll want to check for that in any code you write. It works here because I'm sure that these entries exist in their respective archives.

Issues with saving XML file (C#)

I'm trying to make a config file from an XML file, but I can't figure out how to save the file after I add to it. I can read from the file fine, so I know it's not an issue with where it's located, but I still don't know how to save it.
I've looked around for about 2 hours and can't figure out the problem. I'm know my way around c# but am completely new to XML.
public async Task CreateReaction(string name, DiscordMessage message, DiscordEmoji emoji, DiscordRole role)
{
string path = #"E:\Visual Studio\repos\JustHangoutBot\bin\Debug\netcoreapp1.1\configs\reactions.xml";
XDocument doc = XDocument.Load(path);
await message.CreateReactionAsync(emoji);
XElement root = new XElement(name);
root.Add(new XElement("MessageID", message.Id));
root.Add(new XElement("ReactionID", emoji.Id));
root.Add(new XElement("RoleID", role.Id));
doc.Element("Reactions").Add(root);
byte[] byteArray = Encoding.UTF8.GetBytes(path);
MemoryStream stream = new MemoryStream(byteArray);
doc.Save(stream);
}
I think the problem is somewhere in the last three lines. I've seen tutorials of people saving the file by just using doc.Save("reactions.xml") for example, but I get the error of not being able to convert from string to Stream.
Any help would be appreciated. Thank you in advance!
This will do it:
using (var fileStream = System.IO.File.OpenWrite("path to the file you want to write"))
{
doc.Save(fileStream);
}
When you do this:
byte[] byteArray = Encoding.UTF8.GetBytes(path);
MemoryStream stream = new MemoryStream(byteArray);
doc.Save(stream);
What's happening is
You're opening the file at path and reading it into a byte array.
You're creating a MemoryStream that has those bytes as its content
You're saving that document to the MemoryStream.
Under the hood a MemoryStream is just an array of bytes in memory. So it's writing the file to memory, not to a file.
File.OpenWrite(path) opens a FileStream with the specified path. If the file doesn't exist it creates it. If the file does exist it will overwrite it.
So when you call doc.Save(fileStream) you're writing to the file.

Update a file in a ZipArchive

I have a ZipArchive object which contains an XML file that I am modifying. I then want to return the modified ZipArchive.
Here's the code I have:
var package = File.ReadAllBytes(/* location of existing .zip */);
using (var packageStream = new MemoryStream(package, true))
using (var zipPackage = new ZipArchive(packageStream, ZipArchiveMode.Update))
{
// obtain the specific entry
var myEntry = zipPackage.Entries.FirstOrDefault(entry => /* code elided */));
XElement xContents;
using (var reader = new StreamReader(myEntry.Open()))
{
// read the contents of the myEntry XML file
// then modify the contents into xContents
}
using (var writer = new StreamWriter(myEntry.Open()))
{
writer.Write(xContents.ToString());
}
return packageStream.ToArray();
}
This code throws a "Memory stream is not expandable" exception on the packageStream.ToArray() call.
Can anyone explain what I've done wrongly, and what is the correct way of updating an existing file inside a ZipArchive?
Clearly, ZipArchive wants to expand or resize the ZIP archive stream. However, you have provided a MemoryStream with a fixed stream length (due to using the constructor MemoryStream(byte[], bool), which creates a memory stream with a fixed length that is equal to the length of the array provided to the constructor).
Since ZipArchive wants to expand (or resize) the stream, provide an resizable MemoryStream (using its parameter-less constructor). Then copy the original file data into this MemoryStream and proceed with the ZIP archive manipulations.
And don't forget to reset the MemoryStream read/write position back to 0 after copying the original file data into it, otherwise ZipArchive will only see "End of Stream" when trying to read the ZIP archive data from this stream.
using (var packageStream = new MemoryStream())
{
using (var fs = File.OpenRead(/* location of existing .zip */))
{
fs.CopyTo(packageStream);
}
packageStream.Position = 0;
using (var zipPackage = new ZipArchive(packageStream, ZipArchiveMode.Update))
{
... do your thing ...
}
return packageStream.ToArray();
}
This code here contains one more correction. In the original code in the question, return packageStream.ToArray(); has been placed within the using block of the ZipArchive. At the time this line will be executed, the ZipArchive instance might not yet have written all data to the MemoryStream, perhaps keeping some data still in some internal buffers and/or perhaps having deferred writing some ZIP data structures.
To ensure that the ZipArchive has actually written all necessary data completely to the MemoryStream, it is here sufficient to move return packageStream.ToArray(); outside after the ZipArchive using block. At the end of its using block, the ZipArchive will be disposed which will also ensure that ZipArchive has written all so far yet unwritten data to the stream. Thus, accessing the MemoryStream after the ZipArchive has been disposed off will yield the complete data of the completely updated ZIP archive.
Side note: Do this only with small-ish ZIP files. The MemoryStream will obviously use internal data buffers (arrays) to hold the data in the MemoryStream. However, packageStream.ToArray(); will create a copy of the data in the MemoryStream, so for a period of time the memory requirements of this routine will be a little more than twice the size of the ZIP archive.

How to specify xml file extension while serializing Xml to a GZipStream

I am serializing an object with the following code, which uses GZip and Xml:
FileStream fs = new FileStream(destinationfolder + "/myFileName.gz",
FileMode.Create, FileAccess.Write);
using (var gz = new GZipStream(fs, CompressionMode.Compress)) {
var serializer = new XmlSerializer(typeof(MyObjectType));
serializer.Serialize(gz, myObject);
}
That works fine, with one single problem: The user can open the .gz file with 7Zip (after setting the file association), but then he can't just doubleclick the shown xml inside the .gz file, since it doesn't have the .xml extension (although the content is actually there, xml formatted and all).
Question is: "How can I serialize XML to a GZipStream so that the .xml extension is saved with the file, inside the .gz archive?" I'm using .NET 4.0.
Thanks for reading.
I have figure out a simple way to solve that. If this way should be considered a hack or a pragmatic and fine solution, is up to each one, I think.
Simply set the GZip filename to myFileName.xml.gz. This actually makes the inner file appear as myFileName.xml (trimming out the .gz extension as before).
I hope this won't break in the future...

Creating a zipped Document that containing files from memory stream / byte array in C#

I'm needing to create a zipped document containing files that exist on the server. I am using the Ionic.Zip to do so, and to create a new Package (which is the zip file) I have to have either a path to a physical file or a stream. I am trying to not create an actual file that would be the zip file, instead just create a stream that would exist in memory or something. how can i do this?
Create the package using a MemoryStream then.
You can try the save method in the ZipFile Class. It can save to a stream
try this.
using (MemoryStream ms = new MemoryStream())
{
using (Ionic.Zip.ZipFile zipFile = new Ionic.Zip.ZipFile())
{
zipFile.AddFiles(filesToBeZipped, false, "NewFolder");//filesTobeZipped is a List<string>
zipFile.Save(ms);
}
}
You'll want to use the .AddEntry method on the ZipFile you've created specifying a name and the byte[] containing the actual file data.
ex.
ZipFile zipFile = new ZipFile();
zipFile.AddEntry(file.FileName, file.FileData);
where file.FileName will be the entry name (in the zip file) and file.FileData is the byte array.

Categories

Resources