Динамически создаваемые zip-файлы ZipStream в PHP не открываются в OSX
У меня есть сайт PHP с большим количеством медиа-файлов, и пользователи должны иметь возможность загружать несколько файлов одновременно в формате.zip. Я пытаюсь использовать ZipStream для обслуживания zip-файлов на лету со сжатием в "хранилище", поэтому на самом деле мне не нужно создавать zip-файлы на сервере, так как некоторые файлы огромны, и их сжатие запредельно медленно.
Это прекрасно работает, и полученные файлы могут быть открыты всеми программами zip, которые я пробовал, без ошибок, за исключением стандартной программы разархивирования OS X, Archive Utility. Вы дважды щелкаете по файлу.zip, и утилита архивирования решает, что он не выглядит как настоящий zip, и вместо этого сжимает в файл.cpgz.
Использование unzip или ditto в терминале OS X или StuffIt Expander без проблем распаковывает файл, но мне нужна программа по умолчанию (Archive Utility), чтобы работать ради наших пользователей.
Какие вещи (флаги и т. Д.) В других приемлемых zip-файлах могут заставить Archive Utility думать, что файл не является действительным zip-файлом?
Я читал этот вопрос, который, кажется, описывает аналогичную проблему, но у меня не установлены биты общего поля битов общего назначения, так что это не проблема третьего бита, и я почти уверен, что у меня есть действительные crc-32, потому что когда я нет, WinRAR подходит.
Я рад опубликовать некоторый код или ссылку на "плохой" zip-файл, если это поможет, но я в основном просто использую ZipStream, переводя его в "режим большого файла" и используя "store" в качестве метода сжатия.
Редактировать - я также попробовал алгоритм сжатия "deflate" и получил те же результаты, поэтому я не думаю, что это "магазин". Стоит также отметить, что я извлекаю файлы один раз с сервера хранения и отправляю их по мере их поступления, поэтому решение, требующее загрузки всех файлов перед отправкой чего-либо, не будет жизнеспособным (крайне Пример: 5 ГБ + 20 МБ файлов. Пользователь не может дождаться передачи всех 5 ГБ на почтовый сервер, прежде чем начнется их загрузка, или они подумают, что он поврежден)
Вот 140-байтовый сжатый тестовый zip-файл "store", демонстрирующий такое поведение: http://teknocowboys.com/test.zip
6 ответов
Проблема была в поле "версия, необходимая для извлечения", которое я обнаружил, выполнив шестнадцатеричное сравнение с файлом, созданным ZipStream, и файлом, созданным Info-zip, и проанализировал различия, пытаясь их устранить.
ZipStream по умолчанию устанавливает его в 0x0603. Info-zip устанавливает его в 0x000A. Zip-файлы с прежним значением не открываются в Archive Utility. Возможно, он не поддерживает функции этой версии?
Принудительная установка "версии, необходимой для извлечения" в 0x000A сделала сгенерированные файлы открытыми также в Archive Utility, как и везде.
Изменить: Еще одна причина этой проблемы - если zip-файл был загружен с помощью Safari (версия пользовательского агента>= 537), и вы недостаточно указали размер файла при отправке заголовка Content-Length.
Решение, которое мы используем, состоит в том, чтобы обнаружить Safari >= 537 на стороне сервера, и если это то, что вы используете, мы определим разницу между размером Content-Length и фактическим размером (как вы это делаете, зависит от вашего конкретного приложения) и после вызова $zipStream->finish(), мы выводим chr(0) для достижения правильной длины. Полученный файл технически поврежден, и любой комментарий, который вы поместите в zip-файл, не будет отображаться, но все zip-программы смогут открыть его и извлечь файлы.
IE требует того же взлома, если вы неверно сообщаете свой Content-Length, но вместо загрузки файла, который не работает, он просто не завершит загрузку и выдает "загрузка прервана".
Использовать ob_clean(); и флеш ();
Пример:
$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);
}
У меня была именно эта проблема, но с другой причиной.
В моем случае сгенерированный php zip будет открываться из командной строки, но не через finder в OSX.
Я сделал ошибку, допуская некоторое содержимое HTML в буфер вывода до создания zip-файла и отправки его обратно в качестве ответа.
<some html></....>
<?php
// Output a zip file...
Программа разархивирования командной строки, очевидно, терпимо относилась к этому, но функция разархивирования Mac - нет.
Для тех, кто использует ZipStream в Symfony, вот ваше решение: /questions/17280319/ispolzovanie-zipstream-v-symfony-potokovaya-zagruzka-zip-ne-budet-raspakovyivatsya-s-pomoschyu-archive-utility-na-mac-osx/17280337#17280337
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, как я выяснил.
Это старый вопрос, но я оставляю то, что у меня работало, на случай, если это поможет кому-то другому. При настройке параметров вам нужно установить нулевой заголовок в значение true и включить zip 64 в значение false (хотя это ограничит архив для архива до 4 ГБ):
$options->setZeroHeader(true);
$opt->setEnableZip64(false)
Все остальное по описанию Forer. Решение найдено на https://github.com/maennchen/ZipStream-PHP/issues/71
Инструмент командной строки InfoZip, который я использую как в Windows, так и в Linux, использует версию 20 для поля "версия, необходимая для извлечения" zip. Это необходимо и для PHP, так как сжатие по умолчанию - алгоритм Deflate. Таким образом, поле "версия, необходимая для извлечения" должно быть действительно 0x0014. Если вы измените код "(6 << 8) +3" в указанном классе ZipStream на "20", вы должны получить действительный Zip-файл на разных платформах.
Автор в основном говорит вам, что zip-файл был создан в OS/2 с использованием файловой системы HPFS, а необходимая Zip-версия предшествовала InfoZip 1.0. Не многие реализации знают, что с этим делать дальше;)
Без понятия. Если внешний класс ZipString не работает, попробуйте другой вариант. PHP ZipArchive
Расширение не поможет вам, так как оно не поддерживает потоковую передачу, а только когда-либо пишет в файлы.
Но вы можете попробовать стандартную утилиту Info-zip. Это может быть вызвано изнутри PHP следующим образом:
#header("Content-Type: archive/zip");
passthru("zip -0 -q -r - *.*");
Это приведет к тому, что несжатый zip-файл будет отправлен обратно клиенту.
Если это не поможет, то внешнему интерфейсу MacOS zip, вероятно, не нравятся несжатые файлы. Удалить -0
флаг тогда.