Я@R

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Я@R » Претечи » ZIP средствами PHP.


ZIP средствами PHP.

Сообщений 1 страница 10 из 11

1

Введение
Эта проблема редко возникает на сайтах. Но тут разбираясь во внутреностях WP, я увидела одно прекрасное средство, загрузки и обновления всей системы- это импорт плагинов в виде запакованого файла в формате ZIP. В принципе от этого можно и обойтись, и, используя загрузку через FTP, с последующей распаковкой средствами удаленного сервера, но как то стало просто интересно посмотреть, можно ли организовать подобный сервис в режиме он-лайн, обычными средствами HTTP, так как ручной механизм загрузки и последующей распаковки годится только для IT-специалистов, и совсем не подходит для обычных юзверей, которые постоянно хотят приобщиться к тайнам интернета
Заглянув в «святые-святых» WP, и по-капавшись в его внутренностях, я нашла тот основной файл, с помощью которого и происходит реаллизация этого великолепного сервиса, как загрузка модуля плагина средствами установки запакованного файла, с последующей ее распоковкой на стороне удаленной машины, в конкретную дирректорию, с определенными, для этого случая начальными или подготовительными сервисами и окончательными или завершающими операциями.

Это файл оформлен в виде класса и весит — 237 кило!!! Понятно, что распаковка zip-а не простое дело, но я все же усомнилась в таком не-производительном объеме файла. Первое что насторожило меня — это то что все было обернуто в «классовые» рамки
Вообще применение классов в PHP у меня вызывает некое отторжение, ибо классы не для интерпритаторов, а для компилирующих программ и систем. Интерпритаторы сами по себе — классы и имеют внутри себя великолепную организацию, при трансляции своими особенными методами трансляции с языка в коды. Но этому я посвящу отдельную статью. Понятно почему получился такой объем, ибо в класс вогнали все возможно-необходимые функции при работе с файлами формата ZIP. Для компилятора — это есть хорошо, но интерпритатор, а тем более нам нужна толька одна возможность — только распоковка. Получается, что все остальное просто создано для будущих расширений, и причем опять не всегда совместных, когда одновременно нужно и распаковывать и запаковывать и еще просматривать один файл.

Читатель возможно спросит
— А разве нет ли штатных средств, для работы с ZIP форматами в Инете.
На что я отвечу, что
— Есть — но добавлю — Для того чтоб ими воспользоваться, то необходимо просить хостеров включить подобные средства в стандартный поставляемый PHP, что не всегда возможно, а зачастую просто — просить не кого, ибо мы часитенько работаем на всяких бесплатных рессурсах, где вообще ни чего для юзверей не делают — на халяву.

Вот и я решила разобраться
— как можно обычными средствами PHP просто распаковать ZIP файл,
и его содержимое разместить в нужном мне месте.

Начнем с того — каким способом это делается в WP, а потом, рассмотрев форматы ZIP, привести необходимые скрипты для решения нашей задачи.

2

Как это делается в WordPress
Главными файлами которые выполняют эти операции в WP это два модуля, расположенные в админской части движка.
    *** /wp-admin/include/file.php -     функция unzip_file($from,$to)
    *** /wp-admin/include/class-pclzip.php -    класс — PclZip
Имено последний файл и выполняет все необходимве преобразования с форматом ZIP файла. А конкретно этот класс выполняет следующие основные операции, с различными настройками, передаваемые через аргументы в виде опций и можно посмотреть все возможные операции
  ***  PclZip() : Конструктор объекта
  ***  create() : Создать Zip-архив и добавить в него файлы
  ***  listContent() : Список входящих файлов и структура Zip-архива
  ***  extract() : Экстрактирование контента из Zip-архива
  ***  properties() : получение свойств Zip-архива
WP же использует лишь одну единственную возможность, этого огромадного файла, и только с единственной из парудесятков возможных опций использования данной функции — extract(), - распаковка ZIP-архива в виде массива данных в оперативную память. Укладку получено информации выполняется в другом файле, приведенном ранее.

3

Формат и структура ZIP-архива
Чтобы перейти к непосредственному кодированию, сначала рассмотрим структуру ZIP-архива, и каким образом информация хранится в нем.
Здесь надо сразу сделать одно замечание. В дданной статье не будет рассмотрена возможность паковки и распаковки контента с его предварительной шифровкой, т.е. мы исходно предполагаем, что файлы архива обычные, не шифрованые, и неупакованы, в так называемые, самораспаковывающиеся файлы, которые зачастую используются в системе Windows, для хранения инфы.

Итак начинаем
Zip-архив состоит из трех крупных частей, каждая из которых выполняют свою роль во всем массиве данных
***    Первая часть представляет собой массив спакованых данных . Имено в ней и расположены все части необходимой для нас информации.
***    Вторая часть выполняет роль каталога архива, в которой расположены все описания и информация о включнном в архив файла.
***    Последняя часть представляет собой заголовок , описывающий непосредственно сам ZIP-архив, а также в ней расположен комментарий к архиву, в котором сохраняется информация об архиве. Комментарий может отсутствовать. Вернее он практически всегда отсутвует, но его наличие вернее его отсутствие с возможным присутствием только осложняет жизнь программисту.

4

Каждая из частей начинается уникальным идентификатором, который однозначно определяет часть архива. Этот идентификатор расположен всегда в начале любой записи.
Нашу информацию мы начнем со структуры последней часть архива, которая называется глобальный заголовок. Этот заголовок представляет собой 22-байтную запись, расположенную в конце файла архива, сразу перед комметарием.
Вот его расшифровка.

Название

Размер

Означает

06054B50

4

идентификатор этого заголовка

disk

2

номер диска

disk_start

2

номер диска начального

disk_entries

2

число файлов на диске

entries

2

число файлов в архиве

size

4

размер в байтах дирректории архива

offset

4

смещение до директории архива от начала файла архива

comment_size

2

размер следующего за этими двумя байтами комментарием.

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

5

Cледующим мы рассмотрим вторую часть ZIP-архива, которая предназначена для сохранении информации о содержимом архива, т.е. просто его дирректория. Этот блог необходим для визуального управления архивом при его модификации, частичной или конкретной распаковки, а также обновлением, пополнением и засены разных частей архива. Вот его сруктура

Название

Размер

Означает

02014B50

4

идентификатор этого заголовка.

version

2

версия архива (2.0)

version_extracted

2

версия паковки (1.0)

flag

2

флаг

compression

2

указатель на степень сжатия (0-не сжат)

mtime

2

время последнеймодификации объекта

mdate

2

дата последней модификации объекта

crc

4

циклический контрольный код сrс32

compressed_size

4

размер объекта в байтах после паковки

size

4

размер объекта в байтах исходно

filename_len

2

длина имени файла

extra_len

2

размер дополнительных данных

comment_len

2

размер комментарием.

disk

2

номер носителя.

internal

2

уровень вложения

external

4

внешние данные (10-фолдер)

offset

4

смещенние от начала файла до объекта.

За этим заголовком непосредственно располагаются данные , которые определяются этим заголовком, а имено {имя файла в байтах длиной по полю 11}, {байты из поля 12},{коментарий по полю 13}.
Используя этот блок записей мы однозначно строим структуру архива. Число записей этого блока выбирается из поля 5 блока с сигнатурой  0605..

6

Ну вот наконец мы и подошли к основному блоку данных архива. Опять колличество записей в этом блоке равно числу из поля 5 с сигнатурой 0605…, но его содержание уже другое.
Каждая запись состоит из заголовка и данных.  Заголовок состоит из следующих полей

и Следующих за ним сначала {имя файла в байтах длиной по полю 10}, {байты из поля 11}
и самих данных размером в байтах из поля 8. Наличие паковки определяется либо неравенством полей 8 и 9, но однозначно — не нулевым значением поля 4.

7

Вот в принципе и все. Здесь уместно сделать несколько замечаний . Как видно из структуры заголовков  для данных и дирректории, проглядывается приличное дублирование данных, которое позволяет независимо распаковывать архив, не копаясь в дирректории, а просто сканировать архив с начала файла, поочедно расифровывая сигнатуры данных, считывая как сами данные, так и их заголовки. Такая структура архива позволяет заметно ускорить процес распаковки, применяя меньше затрат для этой, не совсем простой операции.

8

Распаковка
Итак мы подошли к первой реальной распаковке на PHP нашего архива. Нас интересует возможность  закачки на сайт всего архива с ее распаковкой и размещением в дирректории на сервере.
Для того чтоб скачать архив с компьютера или считать его с другого сайта, нужно выполнить некоторые стандартные операции, которые широко освещаны в инете. Поэтому я сейчас не буду касаться этой  темы, а все внимание заострю на моменте конкретной распаковки некого архива, который, на этот момент средствами штатной транспортировки файлов, доставлен на наш сервер. Также я не коснусь проблем конкретного размещения распакованого архива, и буду считать , что фолдер куда мне необходимо разместить его мне известен исходно.
Для чтения заголовков, которые записаны в виде двоичных  данных, в PHP есть две замчательные функция, для работы с двоичными данными, разной длины и размещения. Это функции unpack(); pack(); . Для распаковки нам нужна конкретно unpack, которая не только изымает данные в необходимом нам формате, но и размещает их в предопределенном для нас именованом формате. Чтоб описать все форматы запишем следующий PHP-код

Код:
define("HDZ_FILE","Vid/vver/vflg/vcmp/vmtm/vmdt/
                  Vcrc/Vcsz/Vfsz/vnfln/vexln");
define("LEN_FILE",30);
define("SGN_FILE",0x04034b50);

Первая крнстанта HDZ_FILE задает формат и поля заголовка. Как видно из конкретики формат данных для функции unpack, следующий. Каждый ключ будущих распакованых двоичных данных в массив определяется длиной+типом и именем , разделенное иежду собой прямым слэшем (/). Первая буква каждого ключа определяет тип и формат поля. Не вдаваясь в подробности форматов для этой функции (в принципе это заслуживает отдельной статьи) скажу, что здесь используется только два формата V и v, которые соответственно определяют , что двоичные данные хранятся в виде четырех и двух байтовых слов, причем последовательность их определяется от младшего байта к старшему.
Вторая константа LEN_FILE определяет длину заголовка в байтах, а третья SGN_FILE конкрктно его сигнатуру.

9

Чтобы прочитать одну запись из архивированого файла была определена функция:

Код:
function _zip_item(&$zip){

Эта функция читает из файла определенного ссылкой на дескриптор $zip

Код:
  if($dat=@fread($zip,LEN_FILE)){

Читаем из файла $zip заголовок ввиде цепочки байт длиной определенной константой LEN_FILE

Код:
    $itm = unpack(HDZ_FILE,$dat);

Если чтение успешно, то функцией unpack по формату распаковываем его в масив

Код:
    if($itm['id']!=SGN_FILE) return -1;

Проверяем заголовок на наличии необходимой сигнатуры если нет, то это не наши данные , и возвращаем -1.

Код:
    if($itm['nfln'])
      $itm['name'] = @fread($zip,$itm['nfln']);

Если поле nfln не равно нулю, то читаем из файла $zip — имя нашего файла

Код:
    if($itm['exln'])
      $itm['exta'] = @fread($zip,$itm['exln']);

Также читаем экстра-данные, если они есть.
Все впринципе необходимая нам информация для распаковки имеется
Далее при наличии их — читаем из файла

Код:
    if($itm['csz']){
      $d = @fread($zip,$itm['csz']);

и при не нулевом значения в поле cmp выполняем распаковку данных

Код:
      $itm['dat'] = $itm['cmp']? @gzinflate($d):$d;
    }

Вот в принципе и все.
Осталось за малым, это определить тип записи, а конкретно это файл или директория.Однозначный алгоритм здесь может быть разным, но для простоты я считаю, что перед нами директория если в конце имени присутствует прямой слэш, шаблон которого задается через нерегулярное выражение END_SLASH,  как «/$».

Код:
    $itm['type'] = 0;
    if(ereg(END_SLAH,$itm['name'])){
      $itm['name'] = ereg_replace(
            END_SLAH,"",$itm['name']);
      $itm['type'] = 1;
    }
    return $itm;

результат работы выдается через возврат функции

Код:
  }
  return 0;

В противном случае возвращается 0 — как ошибка.

Код:
}

10

Теперь приведем основную функцию, которая использует функцию чтения и распаковки единицы данных выполняет полноправное действа со всем файлом , включая чтение распаковку и размещение результатов ее вместе с генерацией всех необходимых дирректориев.

Код:
function unzip($src,$to=0){

Вот эта функция, которая и будет выполнять конкретную распаковку. Сразу оговоримся, что функция может быть и другой. Параметры функции определяются так. $src — это полный путь до нашего исходного архива, а $to — это полный путь до места куда наш архив будет распаковываться. Этот параметр инмеет значение по умолчанию равный 0, что означает условие отсутствия перенесения распакованых данных на диск, оставляя их в памяти, передавая через параметр возвращения функции. Такой способ управления назначением, позволяет оперативно управлять полученой информацией.

Код:
  $dst = array();

Усьанавливаем начальный массив для информации в нуль

Код:
  if($fnm = fopen($src,"rb")){

Открываем файл источник, получив его файловый дескриптор

Код:
    while(is_array($d=_zip_item($fnm))){

читаем по порядку все элементы ZIP-архива

Код:
      if($to){

и при наличии необходимости размещения

Код:
        $pth = "$to/{$d['name']}";

формируем полный путь, куда будут размещены текущие распакованые данные

Код:
        if($d['type']) @mkdir($pth);

Если запись определена как дирректория — то создаем ее

Код:
        elseif($out=@fopen($pth,"wb")){

в противном случае — создаем файл для записи с именем полученым из архива

Код:
          if(@fwrite($out,$d['dat'],$d['fsz']))

и сохраняем данные в конкретный файл

Код:
            unset($d['dat']);

и при успешной записи в файл обнуляем содержимое памяти, уничтожив элемент массива

Код:
          @fclose($out);
        }
        $d['dat'] = "to OK!";

при этом сделаем отметку — что данные дошли до места назначения

Код:
      }
      $dst[] = $d;

пополняем пул выходных данных результатом работы

Код:
    }
    @fclose($fnm);

Окончательно закрываем источник, в конце работы

Код:
  }
  if($d<0) return $dst;

если последняя сигнатура была не наша, то это означает конец работы

Код:
  return 0;
}

Вот в принципе и все….


Вы здесь » Я@R » Претечи » ZIP средствами PHP.