diff --git a/Epplus-FixCalcsEdition/EPPlus/EPPlus45-FixCalcsEdition.MultiTarget.csproj b/Epplus-FixCalcsEdition/EPPlus/EPPlus45-FixCalcsEdition.MultiTarget.csproj
index 5ccda9b..2b25777 100644
--- a/Epplus-FixCalcsEdition/EPPlus/EPPlus45-FixCalcsEdition.MultiTarget.csproj
+++ b/Epplus-FixCalcsEdition/EPPlus/EPPlus45-FixCalcsEdition.MultiTarget.csproj
@@ -69,6 +69,10 @@
+
+
+
+
diff --git a/Epplus-FixCalcsEdition/EPPlus/Epplus-FixCalcsEdition.Net48.csproj b/Epplus-FixCalcsEdition/EPPlus/Epplus-FixCalcsEdition.Net48.csproj
index bba5d81..2eda02d 100644
--- a/Epplus-FixCalcsEdition/EPPlus/Epplus-FixCalcsEdition.Net48.csproj
+++ b/Epplus-FixCalcsEdition/EPPlus/Epplus-FixCalcsEdition.Net48.csproj
@@ -79,6 +79,7 @@
+
diff --git a/Epplus-FixCalcsEdition/EPPlus/ExcelWorkbook.cs b/Epplus-FixCalcsEdition/EPPlus/ExcelWorkbook.cs
index 3b05607..e748f50 100644
--- a/Epplus-FixCalcsEdition/EPPlus/ExcelWorkbook.cs
+++ b/Epplus-FixCalcsEdition/EPPlus/ExcelWorkbook.cs
@@ -906,7 +906,7 @@ private void ValidateDataValidations()
}
}
- private void SaveSharedStringHandler(ZipOutputStream stream, CompressionLevel compressionLevel, string fileName)
+ private void SaveSharedStringHandler(Stream stream, CompressionLevel compressionLevel, string fileName)
{
//Packaging.ZipPackagePart stringPart;
//if (_package.Package.PartExists(SharedStringsUri))
@@ -920,11 +920,7 @@ private void SaveSharedStringHandler(ZipOutputStream stream, CompressionLevel co
//}
//StreamWriter sw = new StreamWriter(stringPart.GetStream(FileMode.Create, FileAccess.Write));
- //Init Zip
- stream.CompressionLevel = (CompuMaster.Epplus4.Packaging.Ionic.Zlib.CompressionLevel)compressionLevel;
- stream.PutNextEntry(fileName);
-
- var cache = new StringBuilder();
+ var cache = new StringBuilder();
var sw = new StreamWriter(stream);
cache.AppendFormat("", _sharedStrings.Count);
foreach (string t in _sharedStrings.Keys)
diff --git a/Epplus-FixCalcsEdition/EPPlus/ExcelWorksheet.cs b/Epplus-FixCalcsEdition/EPPlus/ExcelWorksheet.cs
index 7f49ddd..5e4bf1f 100644
--- a/Epplus-FixCalcsEdition/EPPlus/ExcelWorksheet.cs
+++ b/Epplus-FixCalcsEdition/EPPlus/ExcelWorksheet.cs
@@ -2995,14 +2995,8 @@ internal void Save()
}
}
}
- internal void SaveHandler(ZipOutputStream stream, CompressionLevel compressionLevel, string fileName)
+ internal void SaveHandler(Stream stream, CompressionLevel compressionLevel, string fileName)
{
- //Init Zip
- stream.CodecBufferSize = 8096;
- stream.CompressionLevel = (CompuMaster.Epplus4.Packaging.Ionic.Zlib.CompressionLevel)compressionLevel;
- stream.PutNextEntry(fileName);
-
-
SaveXml(stream);
}
diff --git a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackage.cs b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackage.cs
index 8c50704..93b384f 100644
--- a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackage.cs
+++ b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackage.cs
@@ -34,10 +34,9 @@
using System.Linq;
using System.Text;
using System.IO;
+using System.IO.Compression;
using System.Xml;
using CompuMaster.Epplus4.Utils;
-using CompuMaster.Epplus4.Packaging.Ionic.Zip;
-using Ionic.Zip;
namespace CompuMaster.Epplus4.Packaging
{
///
@@ -96,14 +95,24 @@ internal ZipPackage(Stream stream)
{
var rels = new Dictionary();
stream.Seek(0, SeekOrigin.Begin);
- using (ZipInputStream zip = new ZipInputStream(stream))
+ ZipArchive zip = new ZipArchive(stream, ZipArchiveMode.Read, true);
+ if (zip.Entries.Count == 0)
{
- var e = zip.GetNextEntry();
- if(e==null)
+ zip.Dispose();
+ var repairedStream = RepairEmptyCentralDirectory(stream);
+ if (repairedStream == null)
+ {
+ throw new InvalidDataException("The file is not an valid Package file. If the file is encrypted, please supply the password in the constructor.");
+ }
+ zip = new ZipArchive(repairedStream, ZipArchiveMode.Read, false);
+ }
+ using (zip)
+ {
+ if (zip.Entries.Count == 0)
{
throw (new InvalidDataException("The file is not an valid Package file. If the file is encrypted, please supply the password in the constructor."));
}
- if (e.FileName.Contains("\\"))
+ if (zip.Entries[0].FullName.Contains("\\"))
{
_dirSeparator = '\\';
}
@@ -111,40 +120,44 @@ internal ZipPackage(Stream stream)
{
_dirSeparator = '/';
}
- while (e != null)
+ foreach (ZipArchiveEntry e in zip.Entries)
{
- if (e.UncompressedSize > 0)
+ if (e.Length > 0)
{
- var b = new byte[e.UncompressedSize];
- var size = zip.Read(b, 0, (int)e.UncompressedSize);
- if (e.FileName.Equals("[content_types].xml", StringComparison.OrdinalIgnoreCase))
+ byte[] b;
+ using (var entryStream = e.Open())
+ using (var buffer = new MemoryStream())
+ {
+ entryStream.CopyTo(buffer);
+ b = buffer.ToArray();
+ }
+ if (e.FullName.Equals("[content_types].xml", StringComparison.OrdinalIgnoreCase))
{
AddContentTypes(Encoding.UTF8.GetString(b));
hasContentTypeXml = true;
}
- else if (e.FileName.Equals($"_rels{_dirSeparator}.rels", StringComparison.OrdinalIgnoreCase))
+ else if (e.FullName.Equals($"_rels{_dirSeparator}.rels", StringComparison.OrdinalIgnoreCase))
{
ReadRelation(Encoding.UTF8.GetString(b), "");
}
else
{
- if (e.FileName.EndsWith(".rels", StringComparison.OrdinalIgnoreCase))
+ if (e.FullName.EndsWith(".rels", StringComparison.OrdinalIgnoreCase))
{
- rels.Add(GetUriKey(e.FileName), Encoding.UTF8.GetString(b));
+ rels.Add(GetUriKey(e.FullName), Encoding.UTF8.GetString(b));
}
else
{
var part = new ZipPackagePart(this, e);
part.Stream = new MemoryStream();
part.Stream.Write(b, 0, b.Length);
- Parts.Add(GetUriKey(e.FileName), part);
+ Parts.Add(GetUriKey(e.FullName), part);
}
}
}
else
{
}
- e = zip.GetNextEntry();
}
foreach (var p in Parts)
@@ -173,12 +186,69 @@ internal ZipPackage(Stream stream)
{
throw (new InvalidDataException("The file is not an valid Package file. If the file is encrypted, please supply the password in the constructor."));
}
- zip.Close();
- zip.Dispose();
}
}
}
+ // Some older encrypted workbooks have valid central directory records but an
+ // end-of-central-directory record whose entry count and offsets are all zero.
+ // The former streaming ZIP reader ignored that record. Repair a private copy
+ // only when the complete central directory chain can be validated.
+ private static Stream RepairEmptyCentralDirectory(Stream stream)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ byte[] data;
+ using (var copy = new MemoryStream())
+ {
+ stream.CopyTo(copy);
+ data = copy.ToArray();
+ }
+ int end = data.Length - 22;
+ if (end < 46 || !HasZipSignature(data, 0, 0x04034b50) ||
+ !HasZipSignature(data, end, 0x06054b50))
+ {
+ return null;
+ }
+ for (int offset = end + 4; offset < data.Length; offset++)
+ {
+ if (data[offset] != 0) return null;
+ }
+
+ int first = end;
+ int count = 0;
+ while (first >= 46 && count < ushort.MaxValue)
+ {
+ bool found = false;
+ for (int offset = first - 46; offset >= 0; offset--)
+ {
+ if (!HasZipSignature(data, offset, 0x02014b50)) continue;
+ int recordLength = 46 + BitConverter.ToUInt16(data, offset + 28) +
+ BitConverter.ToUInt16(data, offset + 30) + BitConverter.ToUInt16(data, offset + 32);
+ uint localOffset = BitConverter.ToUInt32(data, offset + 42);
+ if ((long)offset + recordLength != first || localOffset >= offset ||
+ !HasZipSignature(data, (int)localOffset, 0x04034b50)) continue;
+ first = offset;
+ count++;
+ found = true;
+ break;
+ }
+ if (!found) break;
+ }
+ if (count == 0 || first == 0) return null;
+
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)count), 0, data, end + 8, 2);
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)count), 0, data, end + 10, 2);
+ Buffer.BlockCopy(BitConverter.GetBytes((uint)(end - first)), 0, data, end + 12, 4);
+ Buffer.BlockCopy(BitConverter.GetBytes((uint)first), 0, data, end + 16, 4);
+ return new MemoryStream(data, false);
+ }
+
+ private static bool HasZipSignature(byte[] data, int offset, uint signature)
+ {
+ return offset >= 0 && offset <= data.Length - 4 &&
+ BitConverter.ToUInt32(data, offset) == signature;
+ }
+
private void AddContentTypes(string xml)
{
var doc = new XmlDocument();
@@ -276,20 +346,23 @@ internal void DeletePart(Uri Uri)
internal void Save(Stream stream)
{
var enc = Encoding.UTF8;
- ZipOutputStream os = new ZipOutputStream(stream, true);
- os.CompressionLevel = (CompuMaster.Epplus4.Packaging.Ionic.Zlib.CompressionLevel)_compression;
+ using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
+ {
/**** ContentType****/
- var entry = os.PutNextEntry("[Content_Types].xml");
+ var entry = archive.CreateEntry("[Content_Types].xml", GetZipCompressionLevel(_compression));
byte[] b = enc.GetBytes(GetContentTypeXml());
- os.Write(b, 0, b.Length);
+ using (var entryStream = entry.Open())
+ {
+ entryStream.Write(b, 0, b.Length);
+ }
/**** Top Rels ****/
- _rels.WriteZip(os, $"_rels/.rels");
+ _rels.WriteZip(archive, $"_rels/.rels", _compression);
ZipPackagePart ssPart=null;
foreach(var part in Parts.Values)
{
if (part.ContentType != ExcelPackage.contentTypeSharedString)
{
- part.WriteZip(os);
+ part.WriteZip(archive);
}
else
{
@@ -299,16 +372,26 @@ internal void Save(Stream stream)
//Shared strings must be saved after all worksheets. The ss dictionary is populated when that workheets are saved (to get the best performance).
if (ssPart != null)
{
- ssPart.WriteZip(os);
+ ssPart.WriteZip(archive);
+ }
}
- os.Flush();
-
- os.Close();
- os.Dispose();
//return ms;
}
+ internal static System.IO.Compression.CompressionLevel GetZipCompressionLevel(CompressionLevel level)
+ {
+ if (level == CompressionLevel.None)
+ {
+ return System.IO.Compression.CompressionLevel.NoCompression;
+ }
+ if ((int)level <= 3)
+ {
+ return System.IO.Compression.CompressionLevel.Fastest;
+ }
+ return System.IO.Compression.CompressionLevel.Optimal;
+ }
+
private string GetContentTypeXml()
{
StringBuilder xml = new StringBuilder("");
diff --git a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackagePart.cs b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackagePart.cs
index 18d82d6..1304e52 100644
--- a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackagePart.cs
+++ b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackagePart.cs
@@ -33,20 +33,21 @@
using System.Linq;
using System.Text;
using System.IO;
+using System.IO.Compression;
using CompuMaster.Epplus4.Packaging.Ionic.Zip;
namespace CompuMaster.Epplus4.Packaging
{
internal class ZipPackagePart : ZipPackageRelationshipBase, IDisposable
{
- internal delegate void SaveHandlerDelegate(ZipOutputStream stream, CompressionLevel compressionLevel, string fileName);
+ internal delegate void SaveHandlerDelegate(Stream stream, CompressionLevel compressionLevel, string fileName);
- internal ZipPackagePart(ZipPackage package, ZipEntry entry)
+ internal ZipPackagePart(ZipPackage package, ZipArchiveEntry entry)
{
Package = package;
Entry = entry;
SaveHandler = null;
- Uri = new Uri(package.GetUriKey(entry.FileName), UriKind.Relative);
+ Uri = new Uri(package.GetUriKey(entry.FullName), UriKind.Relative);
}
internal ZipPackagePart(ZipPackage package, Uri partUri, string contentType, CompressionLevel compressionLevel)
{
@@ -58,7 +59,7 @@ internal ZipPackagePart(ZipPackage package, Uri partUri, string contentType, Com
CompressionLevel = compressionLevel;
}
internal ZipPackage Package { get; set; }
- internal ZipEntry Entry { get; set; }
+ internal ZipArchiveEntry Entry { get; set; }
internal CompressionLevel CompressionLevel;
MemoryStream _stream = null;
internal MemoryStream Stream
@@ -132,7 +133,7 @@ internal SaveHandlerDelegate SaveHandler
get;
set;
}
- internal void WriteZip(ZipOutputStream os)
+ internal void WriteZip(ZipArchive archive)
{
byte[] b;
if (SaveHandler == null)
@@ -142,20 +143,26 @@ internal void WriteZip(ZipOutputStream os)
{
return;
}
- os.CompressionLevel = (CompuMaster.Epplus4.Packaging.Ionic.Zlib.CompressionLevel)CompressionLevel;
- os.PutNextEntry(Uri.OriginalString);
- os.Write(b, 0, b.Length);
+ var entry = archive.CreateEntry(Uri.OriginalString, ZipPackage.GetZipCompressionLevel(CompressionLevel));
+ using (var entryStream = entry.Open())
+ {
+ entryStream.Write(b, 0, b.Length);
+ }
}
else
{
- SaveHandler(os, (CompressionLevel)CompressionLevel, Uri.OriginalString);
+ var entry = archive.CreateEntry(Uri.OriginalString, ZipPackage.GetZipCompressionLevel(CompressionLevel));
+ using (var entryStream = entry.Open())
+ {
+ SaveHandler(entryStream, CompressionLevel, Uri.OriginalString);
+ }
}
if (_rels.Count > 0)
{
string f = Uri.OriginalString;
var name = Path.GetFileName(f);
- _rels.WriteZip(os, (string.Format("{0}_rels/{1}.rels", f.Substring(0, f.Length - name.Length), name)));
+ _rels.WriteZip(archive, (string.Format("{0}_rels/{1}.rels", f.Substring(0, f.Length - name.Length), name)), CompressionLevel);
}
b = null;
}
diff --git a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackageRelationshipCollection.cs b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackageRelationshipCollection.cs
index 3ec65b1..b84e8ba 100644
--- a/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackageRelationshipCollection.cs
+++ b/Epplus-FixCalcsEdition/EPPlus/Packaging/ZipPackageRelationshipCollection.cs
@@ -34,6 +34,7 @@
using System.Text;
using Ionic.Zip;
using System.IO;
+using System.IO.Compression;
using System.Security;
using CompuMaster.Epplus4.Packaging.Ionic.Zip;
@@ -84,7 +85,7 @@ internal ZipPackageRelationshipCollection GetRelationshipsByType(string relation
return ret;
}
- internal void WriteZip(ZipOutputStream os, string fileName)
+ internal void WriteZip(ZipArchive archive, string fileName, CompressionLevel compressionLevel)
{
StringBuilder xml = new StringBuilder("");
foreach (var rel in _rels.Values)
@@ -93,9 +94,12 @@ internal void WriteZip(ZipOutputStream os, string fileName)
}
xml.Append("");
- os.PutNextEntry(fileName);
+ var entry = archive.CreateEntry(fileName, ZipPackage.GetZipCompressionLevel(compressionLevel));
byte[] b = Encoding.UTF8.GetBytes(xml.ToString());
- os.Write(b, 0, b.Length);
+ using (var entryStream = entry.Open())
+ {
+ entryStream.Write(b, 0, b.Length);
+ }
}
public int Count
diff --git a/ExcelOpsTest/Data/CmDataXlsEpplusFixCalcsEditionContractTest.vb b/ExcelOpsTest/Data/CmDataXlsEpplusFixCalcsEditionContractTest.vb
index 7713279..e109953 100644
--- a/ExcelOpsTest/Data/CmDataXlsEpplusFixCalcsEditionContractTest.vb
+++ b/ExcelOpsTest/Data/CmDataXlsEpplusFixCalcsEditionContractTest.vb
@@ -3,6 +3,7 @@ Option Strict On
Imports System.Data
Imports System.IO
+Imports System.IO.Compression
Imports System.Linq
Imports System.Reflection
Imports NUnit.Framework
@@ -76,6 +77,36 @@ Namespace Data
End Using
End Sub
+ '''
+ ''' Verifies that the ZIP backend writes a standard XLSX archive that can be reopened.
+ '''
+
+ Public Sub ZipArchiveRoundTripPreservesWorksheetsAndSharedStrings()
+ Using output As New MemoryStream()
+ Using workbook As New CompuMaster.Epplus4.ExcelPackage()
+ workbook.Workbook.Worksheets.Add("First").Cells(1, 1).Value = "Repeated text"
+ workbook.Workbook.Worksheets.Add("Second").Cells(1, 1).Value = "Repeated text"
+ workbook.SaveAs(output)
+ End Using
+
+ output.Position = 0
+ Using archive As New ZipArchive(output, ZipArchiveMode.Read, True)
+ Assert.That(archive.GetEntry("[Content_Types].xml"), [Is].Not.Null)
+ Assert.That(archive.Entries.Any(Function(entry) entry.FullName.TrimStart("/"c) = "xl/workbook.xml"), [Is].True)
+ Assert.That(archive.Entries.Any(Function(entry) entry.FullName.TrimStart("/"c) = "xl/worksheets/sheet1.xml"), [Is].True)
+ Assert.That(archive.Entries.Any(Function(entry) entry.FullName.TrimStart("/"c) = "xl/worksheets/sheet2.xml"), [Is].True)
+ Assert.That(archive.Entries.Any(Function(entry) entry.FullName.TrimStart("/"c) = "xl/sharedStrings.xml"), [Is].True)
+ End Using
+
+ output.Position = 0
+ Using reopened As New CompuMaster.Epplus4.ExcelPackage(output)
+ Assert.That(reopened.Workbook.Worksheets.Count, [Is].EqualTo(2))
+ Assert.That(reopened.Workbook.Worksheets(0).Cells(1, 1).Text, [Is].EqualTo("Repeated text"))
+ Assert.That(reopened.Workbook.Worksheets(1).Cells(1, 1).Text, [Is].EqualTo("Repeated text"))
+ End Using
+ End Using
+ End Sub
+
'''
''' Verifies that the first worksheet is addressed by its zero-based index.
'''