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

14

今天真是遇到了一些愚蠢的问题,我也变成了一个傻瓜。

我的应用程序会创建一个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上手动创建的普通.zip文件可以在Windows和Mac OS X上轻松解压缩相同的文件。

我在互联网上找到了上述代码,所以我不确定整个过程是否正确。我想知道是否需要使用zipStream.Write()来直接向流中写入?

10个回答

17

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

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

结果,我只需在我的代码中添加以下行:

oZIPStream.UseZip64 = UseZip64.Off;

然后在MacOS X上文件可以正常打开 :-)

谢谢! fred


16

我不能确定,因为我对SharpZipLib和OSX都不是很熟悉,但我仍然可能有一些有用的见解。

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

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

我们进行了一些追踪。目前最有希望的理论是OSX不喜欢每个zip条目头中“通用位字段”中的“位3”。

位3并不新鲜。PKWare在17年前将位3添加到规范中。它旨在支持存档的流生成,就像SharpZipLib工作的方式一样。DotNetZip也有一种方法可以在流出时生成zip文件,并且如果以这种方式使用,则会在zip文件中设置位3,尽管通常情况下,DotNetZip将生成一个未设置位3的zip文件。

据我们所知,当设置第3位时,OSX zip阅读器(无论它是什么 - 就像我说的,我不熟悉OSX)会在zip文件上出现问题。没有第3位的相同zip内容可以被打开。实际上,这并不简单,只需要翻转一位 - 该位的存在信号其他元数据的存在。因此,我使用“第3位”作为所有内容的速记。
因此,理论上第3位导致了问题。我自己还没有测试过。与拥有OSX机器的人的沟通存在一些阻抗失配 - 因此尚未解决。
但是,如果这个理论成立,就可以解释你的情况:WinRar和任何Windows机器都可以打开文件,但OSX不能。
在DotNetZip论坛上,我们讨论了如何处理这个问题。据我所知,OSX zip阅读器已经损坏,无法处理第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也始终是“仅前进”。


非常感谢您的详细回复。如果无法使用SharpZipLib,我可能会转而使用DotNetZip。 - Max
谢谢你的帮助!现在,你能告诉我,DotNetZip是否会在不久的将来支持压缩到非可寻址流而不设置险恶的位3吗? - user51710
我不确定是否可以这样做。解决方法是将其压缩到可寻址流(如MemoryStream或FileStream),然后将该流传输到您的非可寻址流。 - Cheeso

14

所以,我在stackoverflow上搜索了一些更多关于如何使用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();

            }
        }
CRC是循环冗余校验,它是对输入数据的校验和。在Zip文件中,每个条目的标头通常包含一些元数据,包括直到所有条目数据流完才能确定的一些内容:CRC、未压缩大小和压缩大小。在通过流式输出生成Zip文件时,Zip规范允许设置一个位(位3)来指定这三个数据字段将紧随条目数据之后立即写入。
使用ZipOutputStream时,通常当您编写条目数据时,它会被压缩并计算出CRC,然后3个数据字段会立即写入文件数据之后。
您所做的是两次流式传输数据 - 第一次是在编写文件之前隐含地计算文件的CRC。如果我的理论正确,那么正在发生的事情是:在写入文件数据之前向zipStream提供CRC,这使得CRC可以出现在条目标头的正常位置,从而保持OSX的稳定。我不确定其他两个数量(压缩大小和未压缩大小)会发生什么。

1
对我而言,设置entry.Size就足够了,我不需要计算CRC值。 - Sebastian P.

3

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

示例代码:

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

2
我将文件夹名字用反斜线进行分隔... 当我改为正斜线后,它工作了!

1

关于 .cpgz 文件发生的情况是,归档实用工具被一个带有 .zip 扩展名的文件启动。归档实用工具检查该文件并认为它未经压缩,因此将其进行压缩。出于某种奇怪的原因,.cpgz(CPIO 归档 + gzip 压缩)是默认选项。您可以在归档实用工具的首选项中设置其他默认选项。

如果您确实发现这是 macOS 的解压缩程序存在问题,请提交一个错误报告。您还可以尝试使用命令行工具 ditto 进行解压缩,这样可能会得到更好的错误信息。当然,OS X 也附带了 Info-ZIP 实用工具 unzip,但我期望它能正常工作。


0

我同意Cheeso的答案,但如果输入文件大小超过2GB,则byte[] buffer = File.ReadAllBytes(pathname); 将抛出一个IO异常。 所以我修改了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();

            }
        }

0

我遇到了一个类似的问题,但是在Windows 7上。我更新了ICSharpZipLib 0.86.0.518的最新版本(截至本文撰写时)。从那时起,我无法再解压任何使用到目前为止正常工作的代码创建的ZIP归档文件。

提取时出现的错误消息因尝试使用的工具而异:

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

关键在于删除CRC计算,如此处所述:http://community.sharpdevelop.net/forums/t/8630.aspx

因此,我删除了以下行:

entry.Crc = crc.Value

从那时起,我又可以使用任何第三方工具解压ZIP归档文件了。希望这能帮助到某些人。


0

当存档为空(其中没有任何条目)时,我遇到了奇怪的行为,它无法在MAC上打开 - 只会生成cpgz文件。想法是在没有要归档的文件时,在其中放置一个虚拟的.txt文件。


0

有两件事情:

  • 确保您的底层输出流是可寻址的,否则SharpZipLib将无法备份并填充任何您省略的ZipEntry字段(大小、CRC、压缩大小等)。因此,SharpZipLib将强制启用“位3”。该背景在以前的答案中已经解释得很清楚了。

  • 填写ZipEntry.Size或明确设置stream.UseZip64 = UseZip64.Off。默认值是保守地假设流可能非常大。然后解压缩需要“pk 4.5”支持。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接