使用SharpZipLib创建的ZIP文件无法在Mac OS X上打开

唉,今天是愚蠢问题的一天,我是个白痴。

我有一个应用程序,它创建一个zip文件,其中包含某个目录中的一些JPEG。 我使用此代码以便:

  • 从目录中读取所有文件
  • 将它们中的每一个附加到ZIP文件中

using (var outStream = new FileStream("Out2.zip", FileMode.Create)) { using (var zipStream = new ZipOutputStream(outStream)) { foreach (string pathname in pathnames) { byte[] buffer = File.ReadAllBytes(pathname); ZipEntry entry = new ZipEntry(Path.GetFileName(pathname)); entry.DateTime = now; zipStream.PutNextEntry(entry); zipStream.Write(buffer, 0, buffer.Length); } } } 

一切都在Windows下运行良好,当我打开文件,例如使用WinRAR ,文件被提取。 但是一旦我尝试在Mac OS X上解压缩我的存档,它就会创建一个.cpgz文件。 相当无用。

在Windows和Mac OS X上提取在Windows上使用相同文件手动创建的普通.zip文件,没有任何问题。

我在互联网上找到了上面的代码,所以我不确定整件事是否正确。 我想知道是否需要使用zipStream.Write()才能直接写入流?

我不确定,因为我对SharpZipLib或OSX都不是很熟悉,但我仍然可以为你提供一些有用的见解。

我花了一些时间浏览zip规范,实际上我写了DotNetZip ,这是一个.NET的zip库,与SharpZipLib无关。

目前在DotNetZip的用户论坛上,正在讨论由DotNetZip生成的无法在OSX上读取的zip文件。 其中一个使用该库的人遇到的问题与您所看到的类似。 除了我不知道.cpgxz文件是什么。

我们跟踪了一下。 此时最有希望的理论是OSX不喜欢每个zip条目标题中“通用位域”中的“位3”。

第3位并不新鲜。 PKWare在17年前为该规范添加了第3位。 它旨在以SharpZipLib的工作方式支持存档的流式生成。 DotNetZip还有一种方法可以在流式传输时生成zipfile,如果以这种方式使用,它也会在zip文件中设置bit-3,尽管通常DotNetZip会产生一个未设置bit-3的zip文件。

从我们可以看出,当第3位设置时,OSX zip阅读器(无论是什么 – 就像我说我不熟悉OSX)在zip文件上窒息。 在没有第3位的情况下生成相同的zip内容,允许打开zip文件。 实际上它并不像只是翻转一位那么简单 – 位的存在表明存在其他元数据。 所以我使用“第3位”作为所有这些的简写。

所以理论是第3位引起了问题。 我自己没有测试过。 与拥有OSX机器的人的通信存在一些阻抗不匹配 – 因此尚未解决。

但是,如果这个理论成立,它可以解释你的情况:WinRar和任何Windows机器都可以打开文件,但OSX不能。

在DotNetZip论坛上,我们讨论了如何解决这个问题。 尽管我可以说,OSX拉链读取器已损坏,无法处理第3位,因此解决方法是生成第3位未设置的zip文件。 我不知道是否可以说服SharpZipLib这样做。

我知道如果你使用DotNetZip,并使用普通的ZipFile类,并保存到可搜索的流(如文件系统文件),你将得到一个没有第3位设置的zip。 如果理论是正确的,那么每次都应该在Mac上打开没有任何问题。 这是DotNetZip用户报告的结果。 这只是一个结果,所以还没有推广,但看起来似乎有道理。

您的方案的示例代码:

  using (ZipFile zip = new ZipFile() { zip.AddFiles(pathnames); zip.Save("Out2.zip"); } 

只是为了好奇,在DotNetZip中,如果你使用ZipFile类并将其保存到一个不可搜索的流(如ASPNET的Response.OutputStream)或者你在DotNetZip中使用ZipOutputStream类,你将获得第3位设置,它总是只向前写入(不寻求回来)。 我认为SharpZipLib的ZipOutputStream也总是“向前发展”。

今天遇到了完全相同的问题。 我尝试按照提议实现CRC的东西,但它没有帮助。

我最终在这个页面上找到了解决方案: http : //community.sharpdevelop.net/forums/p/7957/23476.aspx#23476

因此,我只需在我的代码中添加此行:

oZIPStream.UseZip64 = UseZip64.Off;

并且该文件在MacOS X上应该打开:-)

干杯弗雷德

所以,我搜索了一些关于如何使用SharpZipLib的例子,我终于让它在Windows和os x上运行了。 基本上我将文件的“Crc32”添加到zip存档中。 不知道这是什么。

这是适合我的代码:

  using (var outStream = new FileStream("Out3.zip", FileMode.Create)) { using (var zipStream = new ZipOutputStream(outStream)) { Crc32 crc = new Crc32(); foreach (string pathname in pathnames) { byte[] buffer = File.ReadAllBytes(pathname); ZipEntry entry = new ZipEntry(Path.GetFileName(pathname)); entry.DateTime = now; entry.Size = buffer.Length; crc.Reset(); crc.Update(buffer); entry.Crc = crc.Value; zipStream.PutNextEntry(entry); zipStream.Write(buffer, 0, buffer.Length); } zipStream.Finish(); // I dont think this is required at all zipStream.Flush(); zipStream.Close(); } } 

来自cheeso的解释:

CRC是循环冗余校验 – 它是条目数据的校验和。 通常,zip文件中每个条目的标题包含一堆元数据,包括在流式传输所有条目数据之前无法知道的一些内容 – CRC,未压缩大小和压缩大小。 当通过流输出生成zipfile时,zip规范允许设置一个位(第3位)以指定这三个数据字段将紧跟在条目数据之后。

如果使用ZipOutputStream,通常在写入条目数据时,会对其进行压缩并计算CRC,并在文件数据之后立即写入3个数据字段。

您所做的是将数据流式传输两次 – 这是第一次隐式地在您编写文件之前计算文件上的CRC。 如果我的理论是正确的,那么正在发生的事情就是:当你在写入文件数据之前向zipStream提供CRC时,这允许CRC出现在条目头中的正常位置,这使OSX保持高兴。 我不确定其他两个量(压缩和未压缩的大小)会发生什么。


我有完全相同的问题,我的错误是(在你的示例代码中)我没有为每个条目提供文件长度。

示例代码:

  ... ZipEntry entry = new ZipEntry(Path.GetFileName(pathname)); entry.DateTime = now; var fileInfo = new FileInfo(pathname) entry.size = fileInfo.lenght; ... 

我用反斜杠分隔文件夹名称…当我将其更改为正斜杠时,它有效!

.cpgz文件发生的事情是Archive Utility是由扩展名为.zip的文件启动的。 Archive Utility检查文件并认为它未被压缩,因此它正在压缩它。 出于某些奇怪的原因, .cpgz (CPIO归档+ gzip压缩)是默认设置。 您可以在Archive Utility的首选项中设置不同的默认值。

如果您确实发现这是OS X的zip解码器的问题,请提交错误 。 您也可以尝试使用ditto命令行工具将其解压缩; 您可能会收到更好的错误消息。 当然,OS X也提供unzip ,Info-ZIP实用程序,但我希望它可以工作。

我同意Cheeso的回答,但如果输入文件大小大于2GB,则byte [] buffer = File.ReadAllBytes(pathname); 将抛出IOexception。 所以我修改了Cheeso代码,它就像所有文件的魅力一样。

  long maxDataToBuffer = 104857600;//100MB using (var outStream = new FileStream("Out3.zip", FileMode.Create)) { using (var zipStream = new ZipOutputStream(outStream)) { Crc32 crc = new Crc32(); foreach (string pathname in pathnames) { tempBuffLength = maxDataToBuffer; FileStream fs = System.IO.File.OpenRead(pathname); ZipEntry entry = new ZipEntry(Path.GetFileName(pathname)); entry.DateTime = now; entry.Size = buffer.Length; crc.Reset(); long totalBuffLength = 0; if (fs.Length <= tempBuffLength) tempBuffLength = fs.Length; byte[] buffer = null; while (totalBuffLength < fs.Length) { if ((fs.Length - totalBuffLength) <= tempBuffLength) tempBuffLength = (fs.Length - totalBuffLength); totalBuffLength += tempBuffLength; buffer = new byte[tempBuffLength]; fs.Read(buffer, 0, buffer.Length); crc.Update(buffer, 0, buffer.Length); buffer = null; } entry.Crc = crc.Value; zipStream.PutNextEntry(entry); tempBuffLength = maxDataToBuffer; fs = System.IO.File.OpenRead(pathname); totalBuffLength = 0; if (fs.Length <= tempBuffLength) tempBuffLength = fs.Length; buffer = null; while (totalBuffLength < fs.Length) { if ((fs.Length - totalBuffLength) <= tempBuffLength) tempBuffLength = (fs.Length - totalBuffLength); totalBuffLength += tempBuffLength; buffer = new byte[tempBuffLength]; fs.Read(buffer, 0, buffer.Length); zipStream.Write(buffer, 0, buffer.Length); buffer = null; } fs.Close(); } zipStream.Finish(); // I dont think this is required at all zipStream.Flush(); zipStream.Close(); } } 

我有一个类似的问题,但在Windows 7.我更新到撰写最新版本的ICSharpZipLib 0.86.0.518。 从那时起,我再也无法解压缩使用到目前为止工作的代码创建的任何ZIP存档。

根据我尝试提取的工具,错误消息有所不同:

  • 未知的压缩方法。
  • 本地标头中的压缩大小与新zip文件中的中央目录标头的大小不匹配。

诀窍是删除这里提到的CRC计算 : http : //community.sharpdevelop.net/forums/t/8630.aspx

所以我删除了以下行:

 entry.Crc = crc.Value 

从那时起,我可以再次使用任何第三方工具解压缩ZIP存档。 我希望这可以帮助别人。

有两件事:

  • 确保您的基础输出流是可搜索的,否则SharpZipLib将无法备份并填写您省略的任何ZipEntry字段(大小,crc,压缩大小……)。 因此,SharpZipLib将强制启用“第3位”。 在以前的答案中已经很好地解释了背景。

  • 填写ZipEntry.Size,或显式设置stream.UseZip64 = UseZip64.Off。 默认情况下保守地假设流可能非常大。 解压缩然后需要“pk 4.5”支持。

当存档为空(其中没有条目)时,我遇到了奇怪的行为,它无法在MAC上打开 – 仅生成cpgz。 我的想法是在其中放入一个虚拟的.txt文件,以防没有归档文件。