У меня есть простой сценарий создания Zip, который копирует загрузку файлов в один каталог, а затем создает файл .zip из этого каталога. Этот подход звучит просто, однако в архивах, которые он создает, есть проблемы с открытием.
Сначала я был сбит с толку из-за того, что архивы хорошо открываются в таких вещах, как 7Zip, WinRar и так далее и так далее. Однако мы не можем использовать встроенную в Windows программу открывания архивов. Чтобы исключить любые проблемы с моим главным сервером, поскольку он использует Nginx + PHPfpm + Fedora 16, я также протестировал на более стандартном сервере, используя Apache и mod_php, работающие на сервере Ubuntu.
В обоих случаях проблема была одна и та же: архив всегда открывался нормально в чистом zip-архиве, но в версии для Windows не получалось. После некоторой случайной копки мне пришла в голову идея открыть файл в Notepad ++, чтобы проверить его начальные заголовки.
Оказывается, что Ziparchive () делает 2 вещи, которые он не должен делать.
Первая проблема проста: он включает в себя полный путь в виде пустого пути в архиве. Так не должно быть, но это так. Это может быть связано с моей рекурсией, хотя я могу жить с этим битом
Вторая проблема — большая проблема, которая заставляет файлы не открываться. Он предваряет нулевой байт в самом начале архива. Все, что мне нужно сделать, это вручную открыть файл в Notepad ++, удалить байт, а затем сохранить его, и вуаля: файл открывается во всем, в том числе в Windows, без проблем.
**
Я никогда не сталкивался с этим раньше, и быстрый Google находит много вещей / проблем с Ziparchive (), но я не мог найти ничего конкретного, как это.
Вот мой метод создания Zip:
private function zipcreate($source, $destination) {
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file) {
$file = str_replace('\\', '/', realpath($file));
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
continue;
$file = realpath($file);
if (is_dir($file) === true) {
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
} else if (is_file($file) === true) {
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
} else if (is_file($source) === true) {
$zip->addFromString(basename($source), file_get_contents($source));
}
return $zip->close();
}
Вызывается, делая:
$this->zipcreate($newdirpath, getcwd()."/$siteid-CompliancePack.zip");
Для справки phpinfo () на основном сервере:
phpinfo () для основного сервера
Как и просили первые 60 байтов файла в шестнадцатеричном виде
[root@sid tmp]# od --format=x1 --read-bytes=60 54709-CompliancePack.zip
0000000 50 4b 03 04 14 00 00 00 08 00 39 4e 92 45 59 28
0000020 27 b3 37 53 00 00 00 f2 00 00 36 00 00 00 47 45
0000040 4e 45 52 41 4c 5f 4e 65 77 20 53 69 74 65 20 20
0000060 48 6f 77 61 72 74 68 20 54 69 6d 62
0000074
[root@sid tmp]#
НОВАЯ РАЗРАБОТКА 🙂
Поэтому я решил попробовать что-то совершенно другое! Я запустил стек WAMP на своем рабочем столе Windows (я обычно тестирую и разрабатываю исключительно на Linux).
Я запустил сайт портала на машине с Windows, считывая данные с основного сервера Linux точно так же, как и сайт реального портала (единственное отличие заключается в том, что живой портал работает в Linux!)
На этот раз файл создан отлично, разница составляет 1 байт! Это в точности тот же код, что и при оперативном запуске на том же внутреннем сервере, с той лишь разницей, что код пользовательского сервера (портала) выполняется на сервере Windows, а не на Linux.
Файл создается внутренним сервером в виде zip-файла, затем кодируется в base64 и возвращается через Nusoap на сервер портала. Который затем передает файл прямо в браузер клиента с помощью следующего кода. SitesClass.downloadCompliancePack — это просто метод, который перемещает все файлы во временную папку, а затем запускает приведенный выше метод zipcreate, поэтому ничего особенного.
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => 'xxx','apppassword' => 'xxx','apikey' => 'xxx','siteid' => 54709));
// Display the result
header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename="54709-CompliancePack.zip"');
$base = json_decode($result[2]);
echo base64_decode($base->FileData);
Так что теперь я еще больше запутался, так как простой base64_decode не должен различаться в Windows и Linux.
ОБНОВЛЕНИЕ январь 2015
Извините за задержку всех тех, кто написал / помог до сих пор, я был немного занят и только нашел время, чтобы посмотреть на это!
Я провел некоторое тестирование на основе информации, размещенной ниже, и сузил точку отказа! Теперь я точно знаю, какой код отвечает за это. Смотрите скриншот ниже.
Шестнадцатеричный вывод в текстовой области создается следующим кодом.
<?php
// get configuration
include "system/config.php";
include "pages/pageclasses/carbon.class.php";
//////////// document action ///////////////
$sid = $_GET['sid'];
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the client instance
$client = new nusoap_client($api_link); // using nosoap_client
// Call the SOAP method
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => $api_username,'apppassword' => $api_password,'apikey' => $api_key,'siteid' => $sid));
// Display the result
$base = json_decode($result[2]);
echo "<textarea>".bin2hex(trim(base64_decode($base->FileData)))."</textarea>";
?>
Другой блок кода, который показывает 20 (шестнадцатеричное пространство), прежде чем это
<?php
// get configuration
include "system/config.php";
include "pages/pageclasses/carbon.class.php";
//////////// document action ///////////////
$sid = $_GET['sid'];
// Pull in the NuSOAP code
require_once('lib/nusoap.php');
// Create the client instance
$client = new nusoap_client($api_link); // using nosoap_client
// Call the SOAP method
$result = $client->call('SitesClass.downloadCompliancePack', array('appusername' => $api_username,'apppassword' => $api_password,'apikey' => $api_key,'siteid' => $sid));
// Display the result
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
$base = json_decode($result[2]);
echo trim(base64_decode($base->FileData));
?>
В обоих случаях код запускается на одном интерфейсном веб-сервере (linux) и на одном внутреннем сервере / сервере веб-служб (linux). Разница лишь в том, что один выводит данные файла в текстовую область, а другой выводит данные файла в браузер в прямом потоке.
Оба блока кода представляют собой все содержимое файла, и перед открытием php или после закрытия php не должно быть пробелов, просто чтобы быть на безопасной стороне, ни в header (), ни в конце строки.
Так что теперь я нахожусь в довольно странной ситуации, когда этот блок кода добавляет случайный пробел в файл до того, как он будет передан
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
echo trim(base64_decode($base->FileData));
О вашем первом выпуске я бы предложил использовать
Эти функции имеют дополнительные аргументы для манипулирования именами файлов:
«Remove_path»
Префикс для удаления из соответствующих путей к файлам перед добавлением в
архив.
И они, кажется, также выполняют работу по обходу файловой системы.
Нулевые символы могут быть связаны с путями.
Здесь упоминается старая ошибка, связанная со старыми файлами: http://grokbase.com/t/php/php-bugs/094pkepf54/48048-new-empty-files-corrupt-zip
Я не думаю, что это актуально, но, возможно, стоит попробовать удалить пустые файлы. (для теста.)
Что касается дополнительного байта в начале вашего файла:
Файл выглядит нормально, когда генерируется на сервере. Очевидно, проблема связана с процессом передачи / кодирования.
Проверьте сценарии, где вы на самом деле обслуживаете файл. Например, когда ваш серверный скрипт выглядит так:
_<?php readfile('zipfile.zip');
и у вас есть пробел (обозначенный подчеркиванием) или любой другой символ в начале вашего скрипта, он будет частью вывода.
Если персонаж не является частью вашего сценария, проверьте включенные сценарии, которые могут нарушить ваш вывод.
Обновление в соответствии с новыми образцами кода:
Попробуйте очистить выходной буфер перед отправкой двоичных данных в браузер:
header('Content-type:application/octet-stream');
header('Content-disposition:attachment;filename="'.$sid.'-CompliancePack.zip"');
ob_clean();
echo trim(base64_decode($base->FileData));