БлогNot. PHP: упаковываем и распаковываем файлы zip на сервере

PHP: упаковываем и распаковываем файлы zip на сервере

Неудавшаяся попытка скрипта, запишем для истории :) Сама по себе работа с классом ZipArchive ничего особенного не представляет, проблемы начинаются, когда в именах файлов есть символы национальных алфавитов.

При архивировании особых проблем не возникло. HTML-форма и браузер считают, что имя файла передано в Юникоде, скрипт тоже должен быть сохранён в Юникоде (UTF-8), тогда при упаковке браузер корректно отдаёт файл с именем archive.zip. Если же кириллицу содержит исходное имя архивируемого файла, то заменяем её на латиницу функцией cyr2lat.

При распаковке архивов начинаются проблемы. В формате ZIP имена файлов хранятся в однобайтовой кодировке, в связи с чем возникает известный баг PHP, при котором разархивация "портит" имена извлекаемых файлов, хотя методы getNameIndex и extractTo из кода работают.

Как исправить баг с именами - показано в коде, но попытка переименовать файлы из архива на "правильные" имена с помощью метода renameName всё равно не удалась, так что это не решение, а только набросок, который возможно, пригодится в будущем.

Также не решена проблема, каким образом получить правильные HTTP-заголовки, чтобы "отдать" браузеру сразу кучу файлов (все файлы архива) за одну транзакцию (см. функцию file_download в коде и её закомментаренный вызов). В реальном приложении хранить на сервере множество загруженных юзером файлов, конечно же, не стоит, нужно отдавать файлы браузеру и удалять их с сервера. Скрипт в том состоянии, что выложен, просто выводит ссылки на загруженные файлы, причём, не факт, что они сработают - именно из-за "испорченных" при извлечении классом ZipArchive имён файлов.

Для краткости также не приведено HTML-обрамление, предполагается, что скрипт размещён на хосте в отдельной папке с файлом .htaccess, устанавливающим кодировку UTF-8:

AddDefaultCharset utf-8

и созданной вложенной папкой unpacked для загружаемых файлов:

вид папок скрипта на хосте
вид папок скрипта на хосте

Вот листинг файла index.php:

<?php
 if (empty($_POST['action'])) {
  echo '
   <form method="POST" action="" enctype="multipart/form-data">
   <input type="radio" name="action" value="unzip"/>Распаковать
   <input type="radio" name="action" value="zip"/>Запаковать
   <br/>
   <input type="file" name="filename"/>
   <br/>
   <input type="submit" value="Выполнить" />
   </form>
 ';
  return;
 }

 define ('ARCHIVE_FOLDER','./unpacked/');
 $error = '';

 if (!class_exists('ZipArchive')) {
  $error = 'Извините, ваша версия PHP не поддерживает работу с архивами ZIP';
 }
 else {
  if ($_FILES['filename']['error'] != UPLOAD_ERR_OK) {
   $error = 'Системная ошибка работы с файлом: '.$_FILES['filename']['error']; 
  }
  else {
   $zip = new ZipArchive;
   if (isset($_POST['action']) and $_POST['action']=='unzip') {
    if ($zip->open($_FILES['filename']['tmp_name']) === true) {
     $filenames = Array();
     for ($i=0; $i<$zip->numFiles; $i++) {
      $filenames[] = $zip->getNameIndex ($i);
      //https://bugs.php.net/bug.php?id=65815
      if ($filenames[$i] === mb_convert_encoding(
       mb_convert_encoding($filenames[$i], "UTF-32", "UTF-8"),
       "UTF-8","UTF-32")) ; //nothing to do
      else $filenames[$i] = mb_convert_encoding ($filenames[$i],'UTF-8','CP866'); 
      //Don't work:
      //$zip->renameName ($zip->getNameIndex($i),$filenames[$i]);
     }
     if (count($filenames)<1) {
      $error = 'В архиве '.$_FILES['filename']['name'].' не обнаружено ни одного файла'; 
     }
     else {
      if ($zip->extractTo (ARCHIVE_FOLDER)!=true) {
       $error = 'Не удалось извлечь файлы в папку '.ARCHIVE_FOLDER;   
      }
      else {
       echo '<p>';
       for ($i=0; $i<count($filenames); $i++) {
        echo '<br><a href="'.ARCHIVE_FOLDER.$zip->getNameIndex($i).'">'.$filenames[$i].'</a>';   
        //file_download (ARCHIVE_FOLDER.$filenames[$i]);
        //@unlink(ARCHIVE_FOLDER.$filenames[$i]);
       }
       echo '</p>';
      }
     }
     $zip->close();
    }
    else {
     $error = 'Не могу открыть архив с именем '.$_FILES['filename']['name'];
    }
   }
   else if (isset($_POST['action']) and $_POST['action']=='zip') {
    $filename = ARCHIVE_FOLDER.'archive.zip';
    if ($zip->open($filename, ZipArchive::CREATE) === true) {
     $zip->addFile ($_FILES['filename']['tmp_name'],cyr2lat($_FILES['filename']['name']));
     $zip->close();
     header('Content-type: application/zip; name=archive.zip');
     header("Content-Transfer-Encoding: Binary");
     header("Content-Length: ".filesize($filename));
     header("Content-Disposition: attachment; filename=\"".basename($filename)."\"");
     readfile ($filename); //отдать файл браузеру
     @unlink($filename); //удалить архив с сервера
    }
    else {
     $error = 'Невозможно создать архив на сервере';
    }
   }
   @unlink ($_FILES['filename']['tmp_name']);
  }
 }
 print_r ($error);//!!!
 if (!empty($error)) echo '<p>'.$error.'; <a href="index.php">Назад</a></p>';

 function cyr2lat ($text) {
 $cyr2lat_replacements = array (
  "А" => "a","Б" => "b","В" => "v","Г" => "g","Д" => "d",
  "Е" => "e","Ё" => "yo","Ж" => "dg","З" => "z","И" => "i",
  "Й" => "y","К" => "k","Л" => "l","М" => "m","Н" => "n",
  "О" => "o","П" => "p","Р" => "r","С" => "s","Т" => "t",
  "У" => "u","Ф" => "f","Х" => "h","Ц" => "ts","Ч" => "ch",
  "Ш" => "sh","Щ" => "csh","Ъ" => "","Ы" => "i","Ь" => "",
  "Э" => "e","Ю" => "yu","Я" => "ya",
  "а" => "a","б" => "b","в" => "v","г" => "g","д" => "d",
  "е" => "e","ё" => "yo","ж" => "dg","з" => "z","и" => "i",
  "й" => "y","к" => "k","л" => "l","м" => "m","н" => "n",
  "о" => "o","п" => "p","р" => "r","с" => "s","т" => "t",
  "у" => "u","ф" => "f","х" => "h","ц" => "ts","ч" => "ch",
  "ш" => "sh","щ" => "sch","ъ" => "","ы" => "i","ь" => "",
  "э" => "e","ю" => "yu","я" => "ya",
  "-" => "_"," " => "_"
 );
  return strtr ($text,$cyr2lat_replacements);
 }

 function file_download ($file) {
  if (file_exists ($file) and is_file($file)) {
   if (ob_get_level()) ob_end_clean();
   header('Content-Description: File Transfer');
   header('Content-Type: application/octet-stream');
   header('Content-Disposition: attachment; filename='.basename($file));
   header('Content-Transfer-Encoding: binary');
   header('Expires: 0');
   header('Cache-Control: must-revalidate');
   header('Pragma: public');
   header('Content-Length: ' . filesize($file));
   readfile($file);
  }
 }
?>
вид формы скрипта
вид формы скрипта

15.03.2017, 11:05 [5921 просмотр]


теги: программирование ошибка php

К этой статье пока нет комментариев, Ваш будет первым