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, когда я открываю файл e. г. с WinRAR файлы извлекаются. Но как только я пытаюсь разархивировать свой архив на Mac OS X, он создает только файл .cpgz. Довольно бесполезно.

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

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

Ответ 1

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

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

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


Ответ 2

Я не знаю точно, потому что я не очень хорошо знаком ни с 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 и сохраните его в несекундном потоке (например, ASPNET Response.OutputStream) или если вы используете класс ZipOutputStream в DotNetZip, который всегда пишет вперед только (не ищет назад). Я думаю, что SharpZipLib ZipOutputStream также всегда "только вперед".

Ответ 3

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

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

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

oZIPStream.UseZip64 = UseZip64.Off;

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

Приветствия Фрэд

Ответ 4

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

Пример кода:

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

Я согласен с ответом Cheeso, однако, если размер входного файла больше 2 ГБ, тогда байт [] buffer = File.ReadAllBytes(путь); будет выдавать исключение 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();

            }
        }

Ответ 8

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

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

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

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

Итак, я удалил строку, которая:

entry.Crc = crc.Value

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

Ответ 9

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

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

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

Ответ 10

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