Exemple ByteArray : lecture d’un fichier .zip

Adobe AIR 1.0 et les versions ultérieures

Cet exemple indique comment lire un fichier .zip simple contenant plusieurs fichiers de types différents. Pour parvenir à lire un tel fichier, les données pertinentes sont extraites des métadonnées pour chacun des fichiers, lesquels sont décompressés dans des classes ByteArray individuelles et écrits dans le poste de travail.

La structure générale d’un fichier .zip repose sur la spécification PKWARE Inc., laquelle est tenue à jour à l’adresse http://www.pkware.com/documents/casestudies/APPNOTE.TXT. Elle commence par l’en-tête et les données du premier fichier de l’archive .zip, suivis de la paire en-tête/données de chaque fichier supplémentaire. (La structure de l’en-tête des fichiers est décrite plus loin dans ce document.) Le fichier .zip comprend éventuellement un enregistrement du descripteur de données (généralement lorsque le fichier zip de sortie a été créé dans la mémoire au lieu d’être enregistré sur un disque). Divers éléments facultatifs viennent ensuite : en-tête de déchiffrement de l’archive, enregistrement des données supplémentaires de l’archive, structure de répertoires centrale, fin Zip64 de l’enregistrement de répertoires central, fin Zip64 du localisateur de répertoires central et fin de l’enregistrement de répertoires.

Le code présenté dans cet exemple a été rédigé dans le seul objectif d’analyser des fichiers zip ne contenant pas de dossiers ; il n’attend pas d’enregistrements de descripteurs de données. Il ne tient pas compte des informations suivant les données du dernier fichier.

Le format de l’en-tête de fichier de chaque fichier est défini de la manière suivante :

signature de l’en-tête de fichier

4 octets

version requise

2 octets

indicateur de bit à usage général

2 octets

méthode de compression

2 octets (8=DEFLATE; 0=UNCOMPRESSED)

heure de la dernière modification du fichier

2 octets

date de la dernière modification du fichier

2 octets

crc-32

4 octets

taille compressée

4 octets

taille décompressée

4 octets

longueur du nom de fichier

2 octets

longueur du champ supplémentaire

2 octets

nom du fichier

variable

champ supplémentaire

variable

L’en-tête du fichier est suivi des véritables données du fichier, au format compressé ou décompressé, suivant l’indicateur de méthode de compression utilisé. L’indicateur est égal à 0 (zéro) si les données du fichier sont décompressées, à 8 si les données sont compressées à l’aide de l’algorithme DEFLATE ou à une autre valeur lorsque les données utilisent d’autres algorithmes de compression.

L’interface utilisateur choisie dans cet exemple se compose d’une étiquette et d’une zone de texte (taFiles). L’application écrit les informations suivantes dans la zone de texte pour chaque fichier détecté dans le fichier .zip : nom du fichier, taille compressée et taille décompressée. Le document MXML suivant définit l’interface utilisateur de la version Flex de l’application :

<?xml version="1.0" encoding="utf-8"?> 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();"> 
    <mx:Script> 
        <![CDATA[ 
            // The application code goes here 
        ]]> 
    </mx:Script> 
    <mx:Form> 
        <mx:FormItem label="Output"> 
            <mx:TextArea id="taFiles" width="320" height="150"/> 
        </mx:FormItem> 
    </mx:Form> 
</mx:WindowedApplication>

Le début du programme effectue les tâches suivantes :

  • Importation des classes requises

    import flash.filesystem.*; 
    import flash.utils.ByteArray; 
    import flash.events.Event;
  • Définition de l’interface utilisateur pour Flash

    import fl.controls.*; 
     
    //requires TextArea and Label components in the Library 
    var taFiles = new TextArea(); 
    var output = new Label(); 
    taFiles.setSize(320, 150); 
    taFiles.move(10, 30); 
    output.move(10, 10); 
    output.width = 150; 
    output.text = "Contents of HelloAir.zip"; 
    addChild(taFiles); 
    addChild(output);
  • Définition de la classe ByteArray bytes

    var bytes:ByteArray = new ByteArray(); 
  • Définition des variables destinées à stocker les métadonnées provenant de l’en-tête du fichier

    // variables for reading fixed portion of file header 
    var fileName:String = new String(); 
    var flNameLength:uint; 
    var xfldLength:uint; 
    var offset:uint; 
    var compSize:uint; 
    var uncompSize:uint; 
    var compMethod:int; 
    var signature:int; 
  • Définition des objets File (zfile) et FileStream (zStream) devant représenter le fichier .zip et indication de l’emplacement du fichier .zip à partir duquel les fichiers sont extraits (fichier intitulé HelloAIR.zip dans le répertoire du poste de travail)

    // File variables for accessing .zip file 
    var zfile:File = File.desktopDirectory.resolvePath("HelloAIR.zip"); 
    var zStream:FileStream = new FileStream();

Dans Flex, le code du programme commence dans la méthode init(), laquelle est appelée en tant que gestionnaire creationComplete pour la balise mx:WindowedApplication racine.

// for Flex 
private function init():void 
{

Le programme commence par ouvrir le fichier .zip en mode READ (lecture).

    zStream.open(zfile, FileMode.READ); 

Il configure ensuite la propriété endian de bytes sur la valeur LITTLE_ENDIAN afin d’indiquer que l’ordre d’octets des champs numériques commence par l’octet le moins important.

    bytes.endian = Endian.LITTLE_ENDIAN; 

Une instruction while() commence ensuite une boucle qui s’interrompt uniquement lorsque la position active dans le flux de fichier est supérieure ou égale à la taille du fichier.

    while (zStream.position < zfile.size) 
    {

La première instruction au sein de la boucle lit les 30 premiers octets du flux de fichier dans la classe ByteArray bytes. Les 30 premiers octets constituent la partie à taille fixe de l’en-tête du premier fichier.

        // read fixed metadata portion of local file header 
        zStream.readBytes(bytes, 0, 30);

Le code lit ensuite un nombre entier (signature) à partir des premiers octets de l’en-tête de 30 octets. La définition du format ZIP indique que la signature de chaque en-tête de fichier correspond à la valeur hexadécimale 0x04034b50 ; si la signature est différente, cela signifie que le code a dépassé la section des fichiers contenus dans le fichier .zip et qu’il ne reste plus aucun fichier à extraire. Dans ce cas, le code quitte immédiatement la boucle while au lieu d’attendre la fin du tableau d’octets.

        bytes.position = 0; 
        signature = bytes.readInt(); 
        // if no longer reading data files, quit 
        if (signature != 0x04034b50) 
        { 
            break; 
        }

La partie suivante du code lit l’octet d’en-tête à la position décalée 8 et stocke la valeur dans la variable compMethod. Cet octet contient une valeur indiquant la méthode de compression appliquée à ce fichier. Plusieurs méthodes de compression sont autorisées, mais en pratique, presque tous les fichiers .zip utilisent l’algorithme de compression DEFLATE. Si le fichier actif est compressé à l’aide de la méthode de compression DEFLATE, compMethod est égal à 8 ; si le fichier n’est pas compressé, compMethod est égal à 0.

        bytes.position = 8; 
        compMethod = bytes.readByte();  // store compression method (8 == Deflate)

Les 30 premiers octets sont suivis par une partie d’en-tête à longueur variable contenant le nom du fichier et, éventuellement, un champ supplémentaire. La variable offset stocke la taille de cette partie. La taille est calculée en ajoutant la longueur du nom de fichier à la longueur du champ supplémentaire, lue à partir de l’en-tête aux décalages 26 et 28.

        offset = 0;    // stores length of variable portion of metadata  
        bytes.position = 26;  // offset to file name length 
        flNameLength = bytes.readShort();    // store file name 
        offset += flNameLength;     // add length of file name 
        bytes.position = 28;    // offset to extra field length 
        xfldLength = bytes.readShort(); 
        offset += xfldLength;    // add length of extra field

Le programme lit ensuite la partie à longueur variable de l’en-tête du fichier pour identifier le nombre d’octets stockés dans la variable offset.

        // read variable length bytes between fixed-length header and compressed file data 
        zStream.readBytes(bytes, 30, offset);

Le programme lit le nom du fichier à partir de la partie variable de l’en-tête et l’affiche dans la zone de texte accompagné des tailles compressée (zippée) et décompressée (initiale) du fichier.

// Flash version 
        bytes.position = 30;  
        fileName = bytes.readUTFBytes(flNameLength); // read file name 
        taFiles.appendText(fileName + "\n"); // write file name to text area 
        bytes.position = 18; 
        compSize = bytes.readUnsignedInt();  // store size of compressed portion 
        taFiles.appendText("\tCompressed size is: " + compSize + '\n'); 
        bytes.position = 22;  // offset to uncompressed size 
        uncompSize = bytes.readUnsignedInt();  // store uncompressed size 
        taFiles.appendText("\tUncompressed size is: " + uncompSize + '\n'); 
        
// Flex version 
        bytes.position = 30;  
        fileName = bytes.readUTFBytes(flNameLength); // read file name 
        taFiles.text += fileName + "\n"; // write file name to text area 
        bytes.position = 18; 
        compSize = bytes.readUnsignedInt();  // store size of compressed portion 
        taFiles.text += "\tCompressed size is: " + compSize + '\n'; 
        bytes.position = 22;  // offset to uncompressed size 
        uncompSize = bytes.readUnsignedInt();  // store uncompressed size 
        taFiles.text += "\tUncompressed size is: " + uncompSize + '\n'; 

L’exemple de code lit le reste du fichier à partir du flux de fichier en octets (bytes) selon la longueur indiquée par la taille compressée, écrasant ainsi l’en-tête de fichier dans les 30 premiers octets. La taille compressée est exacte et ce, même si le fichier n’est pas compressé, car elle équivaut alors à la taille décompressée du fichier.

    // read compressed file to offset 0 of bytes; for uncompressed files 
    // the compressed and uncompressed size is the same 
    if (compSize == 0) continue; 
    zStream.readBytes(bytes, 0, compSize);

Dans la suite de l’exemple, le fichier compressé est décompressé et la fonction outfile() est appelée afin de l’écrire dans le flux du fichier de sortie. Le code passe à outfile() le nom du fichier et le tableau d’octets contenant les données du fichier.

        if (compMethod == 8) // if file is compressed, uncompress 
        { 
            bytes.uncompress(CompressionAlgorithm.DEFLATE); 
        } 
        outFile(fileName, bytes);   // call outFile() to write out the file

Dans l’exemple précédemment mentionné, bytes.uncompress(CompressionAlgorithm.DEFLATE) fonctionne uniquement dans les applications AIR. Pour décompresser les données compressées dans AIR et Flash Player, appelez la fonction inflate() de ByteArray.

Les accolades de fermeture indiquent la fin de la boucle while, de la méthode init() et du code de l’application Flex, à l’exception de la méthode outFile(). L’exécution revient au début de la boucle while et poursuit le traitement des octets suivants du fichier .zip, soit en extrayant un autre fichier soit en mettant un terme au traitement du fichier .zip si le dernier fichier a été traité.

    } // end of while loop 
} // for Flex version, end of init() method and application

La fonction outfile() ouvre un fichier de sortie en mode WRITE (écriture) dans le poste de travail en lui donnant le nom fourni par le paramètre filename. Elle écrit ensuite les données du fichier issues du paramètre data dans le flux du fichier de sortie (outStream) et ferme le fichier.

// Flash version 
function outFile(fileName:String, data:ByteArray):void 
{ 
    var outFile:File = File.desktopDirectory; // destination folder is desktop 
    outFile = outFile.resolvePath(fileName);  // name of file to write 
    var outStream:FileStream = new FileStream(); 
    // open output file stream in WRITE mode 
    outStream.open(outFile, FileMode.WRITE); 
    // write out the file 
    outStream.writeBytes(data, 0, data.length); 
    // close it 
    outStream.close(); 
} 
private function outFile(fileName:String, data:ByteArray):void 
{ 
    var outFile:File = File.desktopDirectory; // dest folder is desktop 
    outFile = outFile.resolvePath(fileName);  // name of file to write 
    var outStream:FileStream = new FileStream(); 
    // open output file stream in WRITE mode 
    outStream.open(outFile, FileMode.WRITE); 
    // write out the file 
    outStream.writeBytes(data, 0, data.length); 
    // close it 
    outStream.close(); 
}