Désarchiver des archives "ar" en PHP

Date de publication : — dernière modification :

Les archives ar sont très peu documentées sur le web, et pourtant, elles sont au coeur des paquets .deb ! C'est un format d'archivage assez ancien, qui permet de regrouper plusieurs fichiers en un seul.

Il m'est arrivé au cours d'un projet en PHP de devoir en décompresser. Malheureusement il n'existait à l'époque aucune librairie permettant de le faire. J'ai donc pris mon courage à deux mains, et ai décidé de faire ma propre implémentation d'un désarchiveur ar en PHP, disponible ici.

La structure d'un fichier ar

Il faut tout d'abord savoir que le fichier ar est divisé en deux partie.

La première de ces deux parties est le header global de l'archive. Contrairement à d'autres formats d'archivage, le header n'a pas d'autre fonctionnalité que d'indiquer qu'il s'agit réellement d'un fichier ar. Il est composé de 7 caractères !<arch> suivi d'un retour à la ligne.

La seconde partie regroupe tous les fichiers de l'archive. Chaque fichier va avoir un header, qui va indiquer plusieurs informations à propos du fichier, les champs de ce header sont indiqués ci-dessous :

Position (en bytes) Taille (bytes) Nom du champ  Format
0 16 Filename ASCII
16 12 File modification timestamp Décimal
28 6 Owner ID Décimal
34 6 Group ID Décimal
40 8 File mode Octal
48 10 File size in bytes Décimal
58 2 File magic 0x60 0x0A

Le file magic est un marqueur, qui précède l'écriture du fichier. Il est important de noter que quand un champ n'est pas rempli entièrement, l'espace restant est rempli avec des espaces (0x20). Le remplissage se fait par la droite.

Implémentation en PHP

Nous allons utiliser fopen() pour ouvrir notre fichier. Cela va nous permettre de lire le fichier petit à petit, sans avoir à créer mettre dans un buffer l'intégralité de notre archive.

On va commencer par ouvrir notre fichier :

<?php
$handle = fopen("monarchive.ar","rb");
?>

Il est intéressant de noter que j'utilise le mode "rb" et non "r" : avec "rb", on ouvre le fichier en mode binaire.

On va maintenant lire l'entête de l'archive à l'aide de fread(), et vérifier qu'il est conforme aux spécifications du format (c'est à dire présence du header global) :

<?php
if (fread($handle,8) != hex2bin("213C617263683E0A"))
    throw new Exception('Bad header');
?>

Si on souhaite bypasser cette vérification, on peut utiliser fseek() :

<?php
fseek($handle,8,SEEK_CUR);
?>

A présent on va lire l'entête du premier fichier :

<?php
$filename = fread($handle,16);
$updated = fread($handle,12);
$owner_id = fread($handle,6);
$group_id = fread($handle,6);
$file_mode = fread($handle,8);
$file_size = fread($handle,10);
?>

Dans la précedente partie de cet article, je vous avais dit que si le champ n'était pas rempli entièrement, il était rempli avec des espaces (0x20 en héxadécimal). Ces espaces peuvent être problématiques dans certaines applications, alors je vais utiliser la fonction rtrim() pour les supprimer :

<?php
$filename = rtrim(fread($handle,16),"\x20");
$updated = rtrim(fread($handle,12),"\x20");
$owner_id = rtrim(fread($handle,6),"\x20");
$group_id = rtrim(fread($handle,6),"\x20");
$file_mode = rtrim(fread($handle,8),"\x20");
$file_size = rtrim(fread($handle,10),"\x20");
?>

Juste avant de lire le fichier, on va lire le file magic :

<?php
if (fread($handle,2) != "\x60\x0A")
    throw new Exception('Bad file header');
?>

On vérifie sa présence pour s'assurer que le fichier a une bonne structure.

A présent on peut lire le fichier :

<?php
$file_content = fread($handle,$file_size);
?>

Et fermer le fichier :

<?php
fclose($handle);
?>

Une fois avoir vu comment désarchiver un fichier, il est facile de modifier le code pour récupérer tous les fichiers d'une archive, en bouclant une partie du code.