Как стать автором
Обновить

RAR: получение списка файлов без PECL

Время на прочтение 9 мин
Количество просмотров 8.4K
Не так давно я писал о получении текста из всевозможных файловых форматов, будь то DOC или PDF. Сегодня мы рассмотрим не менее интересный формат — формат сжатия RAR. Не буду обнадёживать страждущих — сегодня мы только прочитаем список файлов без каких-либо дополнительных расширений PHP. Итак, кому интересно, прошу под кат...

RAR — хороший «плохой» архиватор


Напомню, что RAR разрабатывается нашим соотечественником Евгением Рошалом. От него же и получил своё имя Roshal Archiver. Формат закрытый, что абсолютно не сказалось на его распространении как в России, так и по миру. Почти все рабочие станции, что мне приходилось видеть, были с установленным и подчас крякнутым архиватором RAR.

За время своей разработки и бытности архиватор дорос до 3ей (предположу, что скоро будет и 4ая) версии, что сказалось на большинстве «самопальных» разархиваторов: третья версия привнесла новые алгоритмы сжатия, от чего последние впадали в паранойю и ересь. Тем не менее сайт разработчика содержит достаточный объём всевозможных исходных кодов для разархивации RAR-файлов под разные платформы и среды разработки.

Что до PHP, то PECL-расширение доросло до «стабильной» первой версии и редко когда установлено на хостингах. Расширение, кстати, использует тот самый «unrar», чьи исходные коды лежат на сайте программы. Более того, признаюсь честно, мне не удалось запинать заставить работать расширение под 5.3 (под Windows), под 5.2.11 php_rar.dll заработало, но большинство архивов прочитать не смогло. Не удивлюсь, что все варианты скомпилированной библиотеки под Windows-систему были для «какой-то» другой версии, а компилировать самому не хотелось… поэтому под вечер я сел поглядеть, да посмотреть, что представляет из себя unrar.dll, что можно собрать из исходников на сайте.

RAR — как это?


В связи с закрытостью формата — документация по нему скудная, даже не смотря на тот факт, что исходные коды для разжатия данных есть. Что ж неудивительно — порядка 600 кб исходников рассматривать мало кому захочется. Но тем не менее энтузиасты таки есть (боже упаси, если вы подумали на мою персону :) — поэтому в своё время был создан проект The UniquE RAR File Library, который в разы сократил исходные коды для разархивации файлов, созданных 2ой версией архиватора.

Так вот мне попались на глаза исходники вышеупомянутой библиотеки, а также минимальная, но хоть какая-то, документация по престарелой 2.02 версии архиватора. Что ж, давайте погрузимся в то, как выглядят наши RAR-архивы.

RAR-архив состоит из блоков переменной длины с заголовками по 7 байт каждый. Любой архив содержит как минимум два блока MARK_HEAD и MAIN_HEAD. Первый содержит информацию о том, что перед нами RAR, и выглядит как "52 61 72 21 1a 07 00" в HEX'ах. Третий байт 0х72 как раз таки указывает на то, что это Marker Header. Слово 00 07 в little-endian содержит длину блока. Как раз таки 7 байт.

Второй блок Main Header начинается сразу же после первого и должен содержать 13 байт и иметь маркировочный байт равным 0x73. После него в файле уже начинаются данные — будь-то сжатый файл (маркет 0х74 в третьем байте заголовка блока), комментарий к архиву, дополнительная информация или, к примеру, recovery-запись.

Алгоритм получения списка файлов не сложен (если не брать в расчёт архивы с шифрованной структурой каталогов, чтение которых осталось за рамками этой статьи).

  1. Читаем первые семь байт заголовка. Находим там длину заголовка и дочитываем его до конца;
  2. Проверяем является ли блок «файловым»;
  3. Если «Да», то DWORD на седьмой позиции размер заархивированного файла (а также тот объём данных, что нужно прочесть до следующего блока), следующее двойное слово — размер исходного файла, на позиции №28 — лежат аттрибуты файла (DWORD), а по адресам 26 и 32 находится длина имени файла (2 байта) и само имя. Кроме того, там можно найти дату создания, код ОС в которой был создан файл и CRC;
  4. Если же блок не «файловый», то мы должны прочитать слово на третьей позиции и проверить значение его 15ого бита, что отвечает за дополнительный объём информации, что может идти с блоком. В случае «1» по этой позиции, мы должны пропустить ADD_SIZE байтов (первое двойное слово после заголовка блока);
  5. И так до конца файла...


Сложно? Не очень, в сравнении с каким-нибудь DOC-файлов.

Исходный код


  1. // Функция чтения списка файлов из $filename без использования
  2. // PECL-расширения rar.
  3. function rar_getFileList($filename) {
  4.     // Функция для получения COUNT байтов из строки (little-endian).
  5.     // Чтобы не засорять глобальное пространство функций - отправляем её 
  6.     // вовнуть материнской.
  7.     if (!function_exists("temp_getBytes")) {
  8.         function temp_getBytes($data, $from, $count) {
  9.             $string = substr($data, $from, $count);
  10.             $string = strrev($string);
  11.  
  12.             return hexdec(bin2hex($string));
  13.         }
  14.     }
  15.  
  16.     // Попытка открыть файл
  17.     $id = fopen($filename, "rb");
  18.     if (!$id)
  19.         return false;
  20.  
  21.     // Проверка - является ли файл RAR-архивом
  22.     $markHead = fread($id, 7);
  23.     if (bin2hex($markHead) != "526172211a0700")
  24.         return false;
  25.  
  26.     // Пытаемся прочесть MAIN_HEAD блок
  27.     $mainHead = fread($id, 7);
  28.     if (ord($mainHead[2]) != 0x73)
  29.         return false;
  30.     $headSize = temp_getBytes($mainHead, 5, 2);
  31.  
  32.     // Сдвигаемся на позицию первого "значащего" блока в файле
  33.     fseek($id, $headSize - 7, SEEK_CUR);
  34.  
  35.     $files = array();
  36.     while(!feof($id)) {
  37.         // Читаем загловок блока
  38.         $block = fread($id, 7);
  39.         $headSize = temp_getBytes($block, 5, 2);
  40.         if ($headSize <= 7)
  41.             break;
  42.  
  43.         // Дочитываем остаток блока исходя из длины заголовка по 
  44.         // соответствующему смещению
  45.         $block .= fread($id, $headSize - 7);
  46.         // Если это файловый блок, то начинаем его обрабатывать
  47.         if (ord($block[2]) == 0x74) {
  48.             // Смотрим сколько занимает в архиве запакованный файл и
  49.             // смещаемся к следующей позиции.
  50.             $packSize = temp_getBytes($block, 7, 4);
  51.             fseek($id, $packSize, SEEK_CUR);
  52.  
  53.             // Читаем атрибуты файла: r - read only, h - hidden,
  54.             // s - system, d - directory, a - archived
  55.             $attr = temp_getBytes($block, 28, 4);
  56.             $attributes = "";
  57.             if ($attr & 0x01)
  58.                 $attributes .= "r";
  59.             if ($attr & 0x02)
  60.                 $attributes .= "h";
  61.             if ($attr & 0x04)
  62.                 $attributes .= "s";
  63.             if ($attr & 0x10 || $attr & 0x4000)
  64.                 $attributes = "d";
  65.             if ($attr & 0x20)
  66.                 $attributes .= "a";
  67.  
  68.             // Читаем имя файла, размеры до и после запаковки, CRC и аттрибуты
  69.             $files[] = array(
  70.                 "filename" => substr($block, 32, temp_getBytes($block, 26, 2)),
  71.                 "size_compressed" => $packSize,
  72.                 "size_uncompressed" => temp_getBytes($block, 11, 4),
  73.                 "crc" => temp_getBytes($block, 16, 4),
  74.                 "attributes" => $attributes,
  75.             );
  76.         } else {
  77.             // Если данный блок не файловый, то пропускаем с учётом возможного
  78.             // дополнительного смещения ADD_SIZE
  79.             $flags = temp_getBytes($block, 3, 2);
  80.             if ($flags & 0x8000) {
  81.                 $addSize = temp_getBytes($block, 7, 4);
  82.                 fseek($id, $addSize, SEEK_CUR);
  83.             }
  84.         }
  85.     }
  86.     fclose($id);
  87.  
  88.     // Возвращаем список файлов
  89.     return $files;
  90. }


Код с комментариями вы можете получить на GitHub'е.

Литература


Ну и как обычно литература для ознакомления:



Перспективы


Что до чтения файлов из архивов, то… это теоретически можно сделать на PHP путём рефакторинга библиотеки от UniquE, но это подойдёт лишь для архивов, созданных версией до 2.90. Новые архивы библиотека не прочитает… а разбираться в полутысяче килобайт кода вы сами понимаете.
Теги:
Хабы:
+28
Комментарии 31
Комментарии Комментарии 31

Публикации

Истории

Работа

PHP программист
171 вакансия

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн