ZIP-файл, созданный с помощью SharpZipLib, нельзя открыть в 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 файл. Довольно бесполезно.

Нормальный .zip файл, созданный вручную с теми же файлами в Windows, извлекается без проблем в Windows и Mac OS X.

Я нашел вышеупомянутый код в Интернете, поэтому я не совсем уверен, что все правильно. Интересно, нужно ли это использовать zipStream.Write() для того, чтобы написать прямо в поток?

10 ответов

Решение

Итак, я искал еще несколько примеров того, как использовать SharpZipLib, и я наконец-то заставил его работать на Windows и OSX. В основном я добавил "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, несжатый размер и сжатый размер. При генерации zip-файла через потоковый вывод спецификация zip позволяет установить бит (бит 3), чтобы указать, что эти три поля данных будут следовать непосредственно за входными данными.

Если вы используете ZipOutputStream, обычно, когда вы записываете входные данные, он сжимается и вычисляется CRC, а 3 поля данных записываются сразу после данных файла.

То, что вы сделали, - это потоковая передача данных дважды - в первый раз неявно, когда вы вычисляете CRC для файла перед его записью. Если моя теория верна, то, что происходит, заключается в следующем: когда вы предоставляете CRC для zipStream перед записью данных файла, это позволяет CRC появляться на своем обычном месте в заголовке записи, что делает OSX счастливым. Я не уверен, что происходит с двумя другими величинами (сжатый и несжатый размер).


Получил точно такую ​​же проблему сегодня. Я пытался реализовать материал CRC, как было предложено, но это не помогло.

Я наконец нашел решение на этой странице: http://community.sharpdevelop.net/forums/p/7957/23476.aspx

В результате мне просто нужно было добавить эту строку в мой код:

oZIPStream.UseZip64 = UseZip64.Off;

И файл открывается как следует на MacOS X:-)

Ура фред

Я не знаю наверняка, потому что я не очень хорошо знаком ни с SharpZipLib, ни с OSX, но у меня все еще может быть полезная информация для вас.

Я провел некоторое время, изучая спецификацию zip, и на самом деле я написал DotNetZip, который представляет собой zip-библиотеку для.NET, не связанную с SharpZipLib.

В настоящее время на форумах пользователей DotNetZip продолжается обсуждение zip-файлов, созданных DotNetZip, которые нельзя прочитать в OSX. У одного из людей, использующих библиотеку, возникла проблема, которая кажется похожей на то, что вы видите. За исключением того, что я понятия не имею, что такое файл.cpgxz.

Мы отследили это немного. На данный момент самая многообещающая теория заключается в том, что OSX не нравится "бит 3" в "битовом поле общего назначения" в заголовке каждой записи zip.

Бит 3 не новый. PKWare добавил бит 3 к спецификации 17 лет назад. Он был предназначен для поддержки потоковой генерации архивов так, как работает SharpZipLib. В DotNetZip также есть способ создания zip-файла при его потоковой передаче, и он также устанавливает бит-3 в zip-файле, если используется таким образом, хотя обычно DotNetZip создает zip-файл с битом 3, не установленным в нем.

Из того, что мы можем сказать, когда установлен бит 3, читатель zip OSX (что бы это ни было - как я уже говорил, я не знаком с OSX) душит zip-файл. То же содержимое zip, созданное без бита 3, позволяет открыть файл zip. На самом деле это не так просто, как просто перевернуть один бит - наличие этого бита сигнализирует о наличии других метаданных. Поэтому я использую "бит 3" как сокращение для всего этого.

Таким образом, теория состоит в том, что бит 3 вызывает проблему. Я не проверял это сам. Там было некоторое несоответствие импеданса в связи с человеком, который имеет машину OSX - так что это еще не решено.

Но, если эта теория верна, она объяснит вашу ситуацию: WinRar и любая машина Windows могут открыть файл, а OSX - нет.

На форумах DotNetZip мы обсуждали, что делать с этой проблемой. Насколько я могу судить, читатель zip в OSX не работает и не может обработать бит 3, поэтому обходной путь заключается в создании файла zip с незафиксированным битом 3. Я не знаю, можно ли убедить SharpZipLib сделать это.

Я знаю, что если вы используете DotNetZip, используете обычный класс ZipFile и сохраняете в доступный для поиска поток (например, файл файловой системы), вы получите zip, для которого не установлен бит 3. Если теория верна, она должна открываться без проблем на Mac, каждый раз. Это результат, о котором сообщил пользователь DotNetZip. Это всего лишь один результат, который пока не поддается обобщению, но выглядит правдоподобно.

Пример кода для вашего сценария:

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

Просто для любопытства, в DotNetZip вы получите бит 3, установленный, если вы используете класс ZipFile и сохраните его в потоке без возможности поиска (например, Response.OutputStream в ASPNET) или если вы используете класс ZipOutputStream в DotNetZip, который всегда записывает только вперед (нет ищу обратно). Я думаю, что ZipOutputStream от SharpZipLib также всегда "только вперед".

У меня была точно такая же проблема, моя ошибка была (и в вашем примере кода), что я не указывал длину файла для каждой записи.

Пример кода:

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

Я разделял имена папок с помощью обратной косой черты... когда я изменил это на прямую косую черту, это сработало!

Что происходит с .cpgz файл заключается в том, что утилита архивирования запускается файлом с .zip расширение. Утилита архивирования проверяет файл и считает, что он не сжат, поэтому сжимает его. По какой-то странной причине, .cpgz (Архивация CPIO + сжатие gzip) используется по умолчанию. Вы можете установить другое значение по умолчанию в настройках Archive Utility.

Если вы действительно обнаружите, что это проблема с zip-декодером OS X, пожалуйста, сообщите об ошибке. Вы также можете попробовать использовать ditto инструмент командной строки, чтобы распаковать его; Вы можете получить лучшее сообщение об ошибке. Конечно, OS X также поставляется unzipутилита Info-ZIP, но я ожидаю, что это сработает.

Есть две вещи:

  • Убедитесь, что ваш основной выходной поток доступен для поиска, иначе SharpZipLib не сможет выполнять резервное копирование и заполнять любые поля ZipEntry, которые вы пропустили (размер, crc, сжатый размер, ...). В результате SharpZipLib заставит "бит 3" быть включенным. Фон был хорошо объяснен в предыдущих ответах.

  • Заполните ZipEntry.Size или явным образом установите stream.UseZip64 = UseZip64.Off. По умолчанию предполагается, что поток может быть очень большим. Для распаковки требуется поддержка "pk 4.5".

Однако я согласен с ответом Cheeso, если размер входного файла превышает 2 ГБ, тогда 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();

            }
        }

Я столкнулся со странным поведением, когда архив пуст (нет записей внутри него), он не может быть открыт на MAC - генерирует только cpgz. Идея заключалась в том, чтобы поместить в него фиктивный файл.txt на тот случай, если нет файлов для архивирования.

У меня была похожая проблема, но в Windows 7. Я обновил до последней версии ICSharpZipLib 0.86.0.518. С тех пор я больше не мог распаковывать архивы ZIP, созданные с помощью кода, который работал до сих пор.

Там сообщения об ошибках были разными в зависимости от инструмента, который я пытался извлечь с помощью:

  • Неизвестный метод сжатия.
  • Сжатый размер в локальном заголовке не соответствует размеру заголовка центрального каталога в новом zip-файле.

Что удалось сделать, чтобы удалить расчет CRC, как упомянуто здесь: http://community.sharpdevelop.net/forums/t/8630.aspx

Поэтому я удалил строку, которая является:

entry.Crc = crc.Value

И с тех пор я мог снова разархивировать ZIP-архивы любым сторонним инструментом. Я надеюсь, что это помогает кому-то.

Другие вопросы по тегам