web-dev-qa-db-fra.com

Le moyen le plus simple de télécharger et décompresser des fichiers dans Node.js sur plusieurs plateformes

Nous recherchons simplement une solution simple pour télécharger et décompresser les fichiers .Zip ou .tar.gz dans Node.js, quel que soit le système d'exploitation utilisé.

Je ne sais pas si cela est intégré ou si je dois utiliser une bibliothèque séparée. Des idées? Nous recherchons quelques lignes de code et, dès que le prochain fichier Zip que je souhaite télécharger dans le nœud, c’est une évidence. Sentez-vous comme cela devrait être facile et/ou intégré, mais je ne trouve rien. Merci!

43
Lance Pollard

Commander adm-Zip .

ADM-Zip est une implémentation JavaScript pure pour la compression de données Zip pour NodeJS.

La bibliothèque vous permet de:

  • décompresser les fichiers Zip directement sur le disque ou les mémoires tampons en mémoire 
  • compresser les fichiers et les stocker sur le disque au format .Zip ou dans des tampons compressés
  • mettre à jour le contenu de/ajouter de nouveaux/supprimer des fichiers d'un .Zip existant
28
bryanmac

Nous sommes en 2017 (le 26 octobre pour être exact).

Pour une technologie ancienne et omniprésente telle que unzip, je pense qu’il existe une bibliothèque unzip mature et très populaire, node.js, "stagnante" et "non maintenue" car "complète".

Cependant, la plupart des bibliothèques semblent être complètement terribles ou avoir des commissions récemment comme il y a quelques mois. C'est assez préoccupant ... j'ai donc parcouru plusieurs bibliothèques unzip, lu leur documentation et essayé leurs exemples pour essayer de comprendre WTF. Par exemple, j'ai essayé ceux-ci:

Recommandation principale: yauzl

Fonctionne très bien pour le fichier complètement téléchargé. Pas aussi bien pour le streaming.

Bien documenté. Fonctionne bien. Logique.

2ème choix: node-stream-Zip

antelle's node-stream-Zip semble être le meilleur

Installer:

npm install --save node-stream-Zip

Usage:

'use strict';

var StreamZip = require('node-stream-Zip');

var Zip = new StreamZip({
  file: './example.Zip'
, storeEntries: true
});

Zip.on('error', function (err) { console.error('[ERROR]', err); });

Zip.on('ready', function () {
  console.log('All entries read: ' + Zip.entriesCount);
  //console.log(Zip.entries());
});

Zip.on('entry', function (entry) {
  var pathname = path.resolve('./temp', entry.name);
  if (/\.\./.test(path.relative('./temp', pathname))) {
      console.warn("[Zip warn]: ignoring maliciously crafted paths in Zip file:", entry.name);
      return;
  }

  if ('/' === entry.name[entry.name.length - 1]) {
    console.log('[DIR]', entry.name);
    return;
  }

  console.log('[FILE]', entry.name);
  Zip.stream(entry.name, function (err, stream) {
    if (err) { console.error('Error:', err.toString()); return; }

    stream.on('error', function (err) { console.log('[ERROR]', err); return; });

    // example: print contents to screen
    //stream.pipe(process.stdout);

    // example: save contents to file
    mkdirp(path.dirname(pathname, function (err) {
      stream.pipe(fs.createWriteStream(pathname));
    });
  });
});

Avertissement de sécurité:

Je ne sais pas si cela vérifie entry.name pour trouver des chemins mal conçus qui ne seraient pas résolus correctement (tels que ../../../foo ou /etc/passwd).

Vous pouvez facilement le vérifier vous-même en comparant /\.\./.test(path.relative('./to/dir', path.resolve('./to/dir', entry.name))).

Pros: (Pourquoi je pense que c'est le meilleur?)

  • peut décompresser des fichiers normaux (peut-être pas des fichiers fous avec des extensions étranges)
  • peut diffuser
  • semble ne pas avoir à charger tout le zip pour lire les entrées
  • a des exemples en JavaScript normal (non compilé)
  • n'inclut pas l'évier de la cuisine (c.-à-d. chargement de l'URL, couches S3 ou db)
  • utilise du code existant d'une bibliothèque populaire
  • n'a pas trop de hipster insensé ou ninja-foo dans le code

Les inconvénients:

  • Avale les erreurs comme un hippopotame affamé
  • Lance des chaînes au lieu d'erreurs (pas de traces de pile)
  • Zip.extract() ne semble pas fonctionner (j'ai donc utilisé Zip.stream() dans mon exemple)

Meilleur final: node-unzipper

Installer:

npm install --save unzipper

Usage:

'use strict';

var fs = require('fs');
var unzipper = require('unzipper');

fs.createReadStream('./example.Zip')
  .pipe(unzipper.Parse())
  .on('entry', function (entry) {
    var fileName = entry.path;
    var type = entry.type; // 'Directory' or 'File'

    console.log();
    if (/\/$/.test(fileName)) {
      console.log('[DIR]', fileName, type);
      return;
    }

    console.log('[FILE]', fileName, type);

    // TODO: probably also needs the security check

    entry.pipe(process.stdout/*fs.createWriteStream('output/path')*/);
    // NOTE: To ignore use entry.autodrain() instead of entry.pipe()
  });

Avantages:

  • Semble fonctionner de la même manière que node-stream-Zip, mais avec moins de contrôle
  • Un fork plus fonctionnel de unzip
  • Semble fonctionner en série plutôt qu'en parallèle

Les inconvénients:

  • Évier de cuisine beaucoup? Comprend juste une tonne de choses qui ne sont pas liées à la décompression
  • Lit le fichier entier (par morceau, ce qui est bien), pas seulement une recherche aléatoire
36
CoolAJ86

Node a un support intégré pour gzip et deflate via le module zlib :

var zlib = require('zlib');

zlib.gunzip(gzipBuffer, function(err, result) {
    if(err) return console.error(err);

    console.log(result);
});

Edit: Vous pouvez même pipe les données directement via, par exemple. Gunzip (utilisant demande ):

var request = require('request'),
    zlib = require('zlib'),
    fs = require('fs'),
    out = fs.createWriteStream('out');

// Fetch http://example.com/foo.gz, gunzip it and store the results in 'out'
request('http://example.com/foo.gz').pipe(zlib.createGunzip()).pipe(out);

Pour les archives tar, il y a le module tar de Isaacs, utilisé par npm.

Edit 2: La réponse mise à jour en tant que zlib ne prend pas en charge le format Zip. Cela ne fonctionnera que pour gzip.

yauzl est une bibliothèque robuste pour la décompression. Principes de conception:

  • Suivez les spécifications. Ne pas rechercher les en-têtes de fichiers locaux. Lisez le répertoire central pour les métadonnées du fichier.
  • Ne bloquez pas le fil JavaScript. Utilisez et fournissez des API asynchrones.
  • Gardez l'utilisation de la mémoire sous contrôle. N'essayez pas de mettre en mémoire tampon des fichiers entiers dans RAM à la fois.
  • Jamais crash (si utilisé correctement). Ne laissez pas les fichiers Zip mal formés détruire les applications clientes qui tentent de détecter les erreurs.
  • Attrapez les entrées de noms de fichiers dangereux. Une entrée de fichier Zip génère une erreur si son nom de fichier commence par "/" ou/[A-Za-z]: // ou s'il contient des segments de chemin ".." ou "\" (selon les spécifications).

A actuellement une couverture de test de 97%.

13
andrewrk

J'ai essayé quelques-unes des bibliothèques de décompression de nœuds telles que adm-Zip et unzip, puis j'ai opté pour extract-Zip, qui est une enveloppe autour de yauzl. Semblait le plus simple à mettre en œuvre.

https://www.npmjs.com/package/extract-Zip

var extract = require('extract-Zip')
extract(zipfile, { dir: outputPath }, function (err) {
   // handle err
})
10
Simon Hutchison

J'avais hâte de voir cela pendant longtemps et je n'ai trouvé aucun exemple de travail simple, mais en me basant sur ces réponses, j'ai créé la fonction downloadAndUnzip()

L'utilisation est assez simple:

downloadAndUnzip('http://your-domain.com/archive.Zip', 'yourfile.xml')
    .then(function (data) {
        console.log(data); // unzipped content of yourfile.xml in root of archive.Zip
    })
    .catch(function (err) {
        console.error(err);
    });

Et voici la déclaration:

var AdmZip = require('adm-Zip');
var request = require('request');

var downloadAndUnzip = function (url, fileName) {

    /**
     * Download a file
     * 
     * @param url
     */
    var download = function (url) {
        return new Promise(function (resolve, reject) {
            request({
                url: url,
                method: 'GET',
                encoding: null
            }, function (err, response, body) {
                if (err) {
                    return reject(err);
                }
                resolve(body);
            });
        });
    };

    /**
     * Unzip a Buffer
     * 
     * @param buffer
     * @returns {Promise}
     */
    var unzip = function (buffer) {
        return new Promise(function (resolve, reject) {

            var resolved = false;

            var Zip = new AdmZip(buffer);
            var zipEntries = Zip.getEntries(); // an array of ZipEntry records

            zipEntries.forEach(function (zipEntry) {
                if (zipEntry.entryName == fileName) {
                    resolved = true;
                    resolve(zipEntry.getData().toString('utf8'));
                }
            });

            if (!resolved) {
                reject(new Error('No file found in archive: ' + fileName));
            }
        });
    };


    return download(url)
        .then(unzip);
};
3
Adam

J'ai trouvé le succès avec ce qui suit, fonctionne avec .Zip
(Simplifié ici pour la publication: pas d’erreur de vérification et décompressez simplement tous les fichiers dans le dossier actuel)

function DownloadAndUnzip(URL){
    var unzip = require('unzip');
    var http = require('http');
    var request = http.get(URL, function(response) {
        response.pipe(unzip.Extract({path:'./'}))
    });
}
1
Mtl Dev

Un autre exemple de travail:

var zlib = require('zlib');
var tar = require('tar');
var ftp = require('ftp');

var files = [];

var conn = new ftp();
conn.on('connect', function(e) 
{
    conn.auth(function(e) 
    {
        if (e)
        {
            throw e;
        }
        conn.get('/tz/tzdata-latest.tar.gz', function(e, stream) 
        {
            stream.on('success', function() 
            {
                conn.end();

                console.log("Processing files ...");

                for (var name in files)
                {
                    var file = files[name];

                    console.log("filename: " + name);
                    console.log(file);
                }
                console.log("OK")
            });
            stream.on('error', function(e) 
            {
                console.log('ERROR during get(): ' + e);
                conn.end();
            });

            console.log("Reading ...");

            stream
            .pipe(zlib.createGunzip())
            .pipe(tar.Parse())
            .on("entry", function (e) 
            {    
                var filename = e.props["path"];
                console.log("filename:" + filename);
                if( files[filename] == null )
                {
                    files[filename] = "";
                }
                e.on("data", function (c) 
                {
                    files[filename] += c.toString();
                })    
            });
        });
    });
})
.connect(21, "ftp.iana.org");
0