Динамически созданные zip файлы ZipStream в PHP не будут открываться в OSX

У меня есть сайт PHP с большим количеством мультимедийных файлов, и пользователи должны иметь возможность загружать сразу несколько файлов в формате .zip. Я пытаюсь использовать ZipStream для обслуживания zip на лету с компрессией "store", поэтому мне не нужно создавать zip на сервере, так как некоторые из файлов огромны, и он запрещает сжимать их все.

Это отлично работает, и полученные файлы могут быть открыты каждой почтовой программой, которую я пробовал без ошибок, за исключением программы Unzipping OS X по умолчанию, утилиты архива. Вы дважды щелкните файл .zip, и утилита Archive Utility решит, что она не выглядит реальной почтой и вместо этого сжимается в файл .cpgz.

Использование unzip или ditto в терминале OS X или StuffIt Expander распаковывает файл без проблем, но мне нужна программа по умолчанию (утилита архива) для работы ради наших пользователей.

Какие вещи (флаги и т.д.) в других приемлемых zip файлах могут отключить утилиту Archive Utility, считая, что файл не является допустимым почтовым индексом?

Я прочитал этот вопрос, который, похоже, описывает аналогичную проблему, но у меня нет ни одного бита битового поля общего назначения, чтобы он не был третьим бит, и я уверен, что у меня есть действительный crc-32, потому что, когда я этого не делаю, WinRAR бросает подгонку.

Я рад опубликовать код или ссылку на "плохой" zip файл, если это поможет, но я почти просто использую ZipStream, заставляя его работать в "большом режиме файла" и используя "store" в качестве метод сжатия.

Изменить. Я также попробовал алгоритм сжатия с дефлятом и получил те же результаты, поэтому я не думаю, что это "магазин". Также стоит отметить, что я каждый раз сбрасываю файлы с сервера хранения и отправляю их по мере их поступления, поэтому решение, требующее загрузки всех файлов, прежде чем отправлять что-либо, не будет жизнеспособным (экстремальный пример - 5 ГБ + из 20 МБ файлов. Пользователь не может дождаться, пока все 5 ГБ перейдут на сервер zipping, прежде чем начнется загрузка, или они подумают, что он сломан)

Здесь 140-байтовый, "хранящий" сжатый тестовый zip файл, который проявляет это поведение: http://teknocowboys.com/test.zip

Ответ 1

Проблема заключалась в поле "версия, необходимая для извлечения", которую я обнаружил, выполнив шестнадцатеричный diff в файле, созданным ZipStream, и файл, созданный Info-zip, и пройдя через различия, пытаясь их разрешить.

ZipStream по умолчанию устанавливает его в 0x0603. Info-zip устанавливает его в 0x000A. Почтовые файлы с прежним значением, похоже, не открываются в утилите архива. Возможно, он не поддерживает функции в этой версии?

Заставляя "версию, необходимую для извлечения" на 0x000A, сгенерированные файлы также открываются в программе Archive Utility, как и везде.

Изменить: Другой причиной этой проблемы является то, что zip файл был загружен с помощью Safari (версия агента пользователя >= 537), и вы недооценили размер файла при отправке заголовка Content-Length.

Решение, которое мы используем, - это определить сторону сервера Safari >= 537, и если это то, что вы используете, мы определяем разницу между размером Content-Length и фактическим размером (как вы это делаете, зависит от вашего конкретного приложения) и после вызова $zipStream- > finish(), мы эхо chr (0) достигнем правильной длины. Полученный файл технически искажен и любые комментарии, которые вы помещаете в zip, не будут отображаться, но все zip-программы смогут открыть его и извлечь файлы.

IE требует того же взлома, если вы неверно сообщаете свою длину контента, но вместо загрузки файла, который не работает, он просто не завершит загрузку и выбрасывает "загрузку прерывается".

Ответ 2

используйте ob_clean(); и flush();

Пример:

    $file =  __UPLOAD_PATH . $projectname . '/' . $fileName;

    $zipname = "watherver.zip"
    $zip = new ZipArchive(); 
    $zip_full_path_name = __UPLOAD_PATH . $projectname . '/' . $zipname;
    $zip->open($zip_full_path_name, ZIPARCHIVE::CREATE);
    $zip->addFile($file); // Adding one file for testing
    $zip->close();

    if(file_exists($zip_full_path_name)){
        header('Content-type: application/zip');
        header('Content-Disposition: attachment; filename="'.$zipname.'"');
        ob_clean();
        flush();
        readfile($zip_full_path_name);
        unlink($zip_full_path_name);
    }

Ответ 3

У меня была эта точная проблема, но с другой причиной.

В моем случае созданный php zip будет открываться из командной строки, но не через finder в OSX.

Я допустил ошибку, разрешив некоторое содержимое HTML в выходной буфер до создания zip файла и отправив его обратно в ответ.

<some html></....>
<?php

// Output a zip file...

Программа unzip командной строки, очевидно, была терпима к этому, но функция разблокировки Mac не была.

Ответ 4

Не знаю. Если внешний класс ZipString не работает, попробуйте другой вариант. Расширение PHP ZipArchive вам не поможет, поскольку оно не поддерживает потоковое вещание, а только записывает файлы.

Но вы можете попробовать стандартную утилиту Info-zip. Он может быть вызван из PHP следующим образом:

#header("Content-Type: archive/zip");
passthru("zip -0 -q -r - *.*");

Это приведет к несжатому zip файлу, который будет отправлен обратно клиенту.

Если это не поможет, тогда интерфейс MacOS zip, вероятно, не понравится несжатым. Удалите флаг -0.

Ответ 5

Средство командной строки InfoZip, которое я использую, как в Windows, так и в Linux, использует версию 20 для поля "версия, необходимая для извлечения". Это необходимо и для PHP, поскольку сжатие по умолчанию - это алгоритм Deflate. Таким образом, поле "version needed to extract" должно действительно быть 0x0014. Если вы измените код "(6 < 8) +3" в ссылочном классе ZipStream на "20", вы должны получить действительный файл Zip на разных платформах.

В основном автор говорит вам, что zip файл был создан в OS/2 с использованием файловой системы HPFS, а для Zip-версии была необходима InfoZip 1.0. Не многие реализации знают, что делать с этим больше;)

Ответ 6

Для тех, кто использует ZipStream в Symfony, здесь ваше решение: fooobar.com/info/519724/...

use Symfony\Component\HttpFoundation\StreamedResponse;
use Aws\S3\S3Client;    
use ZipStream;

//...

/**
 * @Route("/zipstream", name="zipstream")
 */
public function zipStreamAction()
{
    //test file on s3
    $s3keys = array(
      "ziptestfolder/file1.txt"
    );

    $s3Client = $this->get('app.amazon.s3'); //s3client service
    $s3Client->registerStreamWrapper(); //required

    $response = new StreamedResponse(function() use($s3keys, $s3Client) 
    {

        // Define suitable options for ZipStream Archive.
        $opt = array(
                'comment' => 'test zip file.',
                'content_type' => 'application/octet-stream'
              );
        //initialise zipstream with output zip filename and options.
        $zip = new ZipStream\ZipStream('test.zip', $opt);

        //loop keys useful for multiple files
        foreach ($s3keys as $key) {
            // Get the file name in S3 key so we can save it to the zip 
            //file using the same name.
            $fileName = basename($key);

            //concatenate s3path.
            $bucket = 'bucketname';
            $s3path = "s3://" . $bucket . "/" . $key;        

            //addFileFromStream
            if ($streamRead = fopen($s3path, 'r')) {
              $zip->addFileFromStream($fileName, $streamRead);        
            } else {
              die('Could not open stream for reading');
            }
        }

        $zip->finish();

    });

    return $response;
}

Если ваш ответ действия с контроллером не является StreamedResponse, скорее всего, вы получите поврежденный zip, содержащий html, как я узнал.