Unzipping a password protected file in Node.js
Asked Answered
H

4

18

Is there a library that I can use to unzip a password protected file (Site makes me put a password on the file when downloading it)? There are a ton of libraries to unzip normal files, but none that I can find that will do it with a password.

Here I found some helpful starters. But I would rather not resort to using child_process and using built in unix unzipping functionality, but that may be my last resort. I would even be up to preforming my own manipulation on the encrypted code, but I'm unable to even find out how to determine the encryption type (it seems to just be very standard, since i can do it in the terminal).

Again I would rather not do this, but I'm afraid it is my only option so I have tried the following:

var fs = require('fs')
, unzip = require('unzip')
, spawn = require('child_process').spawn
, expand = function(filepath, cb) {
     var self = this
     , unzipStream = fs.createReadStream(filepath).pipe(unzip.Parse())
     , xmlData = '';

     unzipStream.on('entry', function (entry) {
            var filename = entry.path;
           // first zip contains files and one password protected zipfile. 
           // Here I can just do pipe(unzip.Parse()) again, but then i just get a giant encoded string that I don't know how to handle, so i tried other things bellow.
           if(filename.match(/\.zip$/)){
                 entry.on('data', function (data) {
                     var funzip = spawn('funzip','-P','ThisIsATestPsswd','-');
                     var newFile = funzip.stdin.write(data);

            // more closing code...

Then I sort of lost what to do. I tried writing newFile to a file, but that just said [object].

Then I tried just doing something simpler by changing the last 3 lines to

   fs.writeFile('./tmp/this.zip', data, 'binary');
   var newFile = spawn('unzip','-P','ThisIsATest','./tmp/this.zip');
   console.log('Data: ' + data);

But data isn't anything useful just [object Object] again. I can't figure out what to do next to get this new unzipped file into either a working file, or a readable string.

I am super new to Node and all the async/listeners being fired by other processes is a little confusing still, so I'm sorry if any of this makes no sense. Thanks so much for the help!

EDIT:


I have now added the following code:

var fs = require('fs')
  , unzip = require('unzip')
  , spawn = require('child_process').spawn
  , expand = function(filepath, cb) {
    var self = this
    , unzipStream = fs.createReadStream(filepath)
      .pipe(unzip.Parse())
    , xmlData = '';

      unzipStream.on('entry', function (entry) {
        var filename = entry.path
            , type = entry.type // 'Directory' or 'File'
            , size = entry.size;
        console.log('Filename: ' + filename);

        if(filename.match(/\.zip$/)){
            entry.on('data', function (data) {
              fs.writeFile('./lib/mocks/tmp/this.zip', data, 'binary');
              var newFile = spawn('unzip','-P','ThisIsATestPassword', '-','../tmp/this.zip');
              newFile.stdout.on('data', function(data){
                 fs.writeFile('./lib/mocks/tmp/that.txt', data); //This needs to be something different
                    //The zip file contains an archive of files, so one file name shouldn't work
              });

           });
          } else { //Not a zip so handle differently }
       )};
    };

This seems to be really close to what I need, but when the file is written all it has is the options list for unzip:

UnZip 5.52 of 28 February 2005, by Info-ZIP.  Maintained by C. Spieler.  Send
bug reports using http://www.info-zip.org/zip-bug.html; see README for details.

Usage: unzip [-Z] [-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]
  Default action is to extract files in list, except those in xlist, to exdir;
  file[.zip] may be a wildcard.  -Z => ZipInfo mode ("unzip -Z" for usage).

  -p  extract files to pipe, no messages     -l  list files (short format)
  -f  freshen existing files, create none    -t  test compressed archive data
  -u  update files, create if necessary      -z  display archive comment
  -x  exclude files that follow (in xlist)   -d  extract files into exdir

modifiers:                                   -q  quiet mode (-qq => quieter)
  -n  never overwrite existing files         -a  auto-convert any text files
  -o  overwrite files WITHOUT prompting      -aa treat ALL files as text
  -j  junk paths (do not make directories)   -v  be verbose/print version info
  -C  match filenames case-insensitively     -L  make (some) names lowercase
  -X  restore UID/GID info                   -V  retain VMS version numbers
  -K  keep setuid/setgid/tacky permissions   -M  pipe through "more" pager
Examples (see unzip.txt for more info):
  unzip data1 -x joe   => extract all files except joe from zipfile data1.zip
  unzip -p foo | more  => send contents of foo.zip via pipe into program more
  unzip -fo foo ReadMe => quietly replace existing ReadMe if archive file newer

I'm not sure if the input was wrong since this looks like the error for unzip. Or if I am just writing the wrong content. I would have thought that it would just preform like it does normally from the console--just adding all files as they are extracted to the directory. while I would love to be able to just read all content from a buffer, the - option doesn't seem to do that and so I would settle for the files just being added to the directory. Thanks so much to anyone with any suggestions!

Edit 2


I was able to get this working, not the best way probably but it works at least using this line:

var newFile = spawn('unzip', [ '-P','ThisIsATestPassword', '-d','./lib/tmp/foo','./lib/mocks/tmp/this.zip' ])

This will just unzip all the files into the directory and then I was able to read them from there. My mistake was that the second parameter has to be an array.

Hangbird answered 29/5, 2014 at 6:16 Comment(4)
Use console.log('Data: ', data);. You concatenating string and object, so object converts to string [object Object].Monteith
When I use this logging I can see that the data object has _events: {data: function}. How can you trigger that data event? I tried data.triggerEvent('data'), but that just came up with an undefined method triggerEvent.Hangbird
Probably a better question is after doing the second option, what object am I dealing with. It has a stdin and a stdout property, does that mean it is a readStream and I can use the same methods to access the data in it?Hangbird
@Hangbird It appears you found an answer correct? If so I would post it as an answer.Woolridge
H
9

I was able to get this working, not the best way probably but it works at least using this line:

var newFile = spawn('unzip', [ '-P','ThisIsATestPassword', '-d','./lib/tmp/foo','./lib/mocks/tmp/this.zip' ])

This will just unzip all the files into the directory and then I was able to read them from there. My mistake was that the second parameter has to be an array.

Hangbird answered 13/11, 2015 at 2:59 Comment(0)
P
5

I found the solution using unzipper.

Pasting the code from this blog

const unzipper = require('unzipper');

(async () => {
  try {
    const directory = await unzipper.Open.file('path/to/your.zip');
    const extracted = await directory.files[0].buffer('PASSWORD');
    console.log(extracted.toString()); // This will print the file content
  } catch(e) {
    console.log(e);
  }
})();

As @codyschaaf mentioned in his answer, we can use spawn or some other child_process but they're not always OS agnostic. So if I'm using this in production, I'll always go for an OS-agnostic solution if it exists.

Hope this helps someone.

Privet answered 22/11, 2019 at 15:31 Comment(2)
for me, this solution gets stuck at const extracted = await directory...........no error, just the execution stopsOzieozkum
Unfortunately, this solution is not working for AWS-256 encrypted files :/ Only for ZipCryptoStoss
C
2

I tried the spawn approach (the spawnSync actually worked better).

const result = spawnSync('unzip', ['-P', 'password', '-d', './files', './files/file.zip'], { encoding: 'utf-8' })

Nonetheless, this approach did not fully work as it introduced a new error:

Archive:  test.zip
   skipping: file.png                need PK compat. v5.1 (can do v4.6)

Eventually, I ended up going with 7zip approach:

import sevenBin from '7zip-bin'
import seven from 'node-7z'

const zipPath = './files/file.zip'
const downloadDirectory = './files'

const zipStream = seven.extractFull(zipPath, downloadDirectory, {
  password: 'password',
  $bin: sevenBin.path7za
})

zipStream.on('end', () => {
  // Do stuff with unzipped content
})
Craving answered 2/6, 2021 at 8:13 Comment(1)
I am trying this 7zip approach, however I get the following error. It seems it does not find the binaries in the PATH? ` Error: spawn 7za ENOENT at Process.ChildProcess._handle.onexit (internal/child_process.js:268:19) at onErrorNT (internal/child_process.js:470:16) at processTicksAndRejections (internal/process/task_queues.js:84:21) `Lemoine
R
0

To unzip password protected zipped multilevel folder try the following code. I am using the unzipper npm.

unzipper.Open.file(contentPath + filename).then((mainDirectory) => {
return new Promise((resolve, reject) => {
  let maindirPath = mainDirectory.files[0].path;
  let patharray = maindirPath.split("/")
  let temppath = destinationPath+patharray[0];
   fs.mkdirSync(temppath);//create parent Directory 
 
    // Iterate through every file inside there (this includes directories and files in subdirectories)
    for (let i = 0; i < mainDirectory.files.length; i++) {
        const file = mainDirectory.files[i];
        let filepath = Distinationpath + file.path
        
        if(file.path.endsWith("/")) {
            fs.mkdirSync(filepath);
        }
        else {
     
            file.stream(password).pipe(fs.createWriteStream(filepath))
                .on('finished', resolve)
                .on('error', reject);
        }
    }
});
});
Rivero answered 14/2, 2022 at 12:59 Comment(1)
This doesn't function, you also have a "Distinationpath" and a "destinationPath"Defeatism

© 2022 - 2024 — McMap. All rights reserved.