diff --git a/lib/Open/directory.js b/lib/Open/directory.js index 88ea27d..b8a0d56 100644 --- a/lib/Open/directory.js +++ b/lib/Open/directory.js @@ -3,7 +3,7 @@ const unzip = require('./unzip'); const BufferStream = require('../BufferStream'); const parseExtraField = require('../parseExtraField'); const path = require('path'); -const Writer = require('fstream').Writer; +const fs = require('fs-extra'); const parseDateTime = require('../parseDateTime'); const parseBuffer = require('../parseBuffer'); const Bluebird = require('bluebird'); @@ -163,16 +163,24 @@ module.exports = function centralDirectory(source, options) { if (extractPath.indexOf(opts.path) != 0) { return; } - const writer = opts.getWriter ? opts.getWriter({path: extractPath}) : Writer({ path: extractPath }); - - return new Promise(function(resolve, reject) { - entry.stream(opts.password) - .on('error', reject) - .pipe(writer) - .on('close', resolve) - .on('error', reject); - }); - }, { concurrency: opts.concurrency > 1 ? opts.concurrency : 1 }); + + return fs.ensureFile(extractPath).then(() => { + const writer = opts.getWriter + ? opts.getWriter({ path: extractPath }) + : fs.createWriteStream(extractPath); + + return new Promise(function (resolve, reject) { + entry + .stream(opts.password) + .on('error', reject) + .pipe(writer) + .on('close', resolve) + .on('error', reject); + }); + }); + }, + { concurrency: opts.concurrency > 1 ? opts.concurrency : 1 } + ); }); }; diff --git a/lib/Open/index.js b/lib/Open/index.js index 83c349b..83da78f 100644 --- a/lib/Open/index.js +++ b/lib/Open/index.js @@ -1,6 +1,7 @@ const fs = require('graceful-fs'); const directory = require('./directory'); const Stream = require('stream'); +const axios = require('axios'); module.exports = { buffer: function(buffer, options) { @@ -37,7 +38,7 @@ module.exports = { return directory(source, options); }, - url: function(request, params, options) { + url: function(params, options) { if (typeof params === 'string') params = {url: params}; if (!params.url) @@ -45,25 +46,33 @@ module.exports = { params.headers = params.headers || {}; const source = { - stream : function(offset, length) { - const options = Object.create(params); - const end = length ? offset + length : ''; - options.headers = Object.create(params.headers); - options.headers.range = 'bytes='+offset+'-' + end; - return request(options); + stream: function (offset, length) { + const stream = Stream.PassThrough(); + const headers = Object.assign({}, params.headers, { + Range: `bytes=${offset}-${length ? offset + length - 1 : ''}`, + }); + + axios + .get(params.url, { headers, responseType: 'stream' }) + .then((response) => { + response.data.pipe(stream); + }) + .catch((error) => { + stream.emit('error', error); + }); + + return stream; }, size: function() { - return new Promise(function(resolve, reject) { - const req = request(params); - req.on('response', function(d) { - req.abort(); - if (!d.headers['content-length']) - reject(new Error('Missing content length header')); - else - resolve(d.headers['content-length']); - }).on('error', reject); - }); - } + return axios + .head(params.url, { headers: params.headers }) + .then((response) => { + if (!response.headers['content-length']) { + throw new Error('Missing content length header'); + } + return parseInt(response.headers['content-length'], 10); + }); + }, }; return directory(source, options); diff --git a/lib/extract.js b/lib/extract.js index 31d725a..002dd08 100644 --- a/lib/extract.js +++ b/lib/extract.js @@ -1,7 +1,7 @@ module.exports = Extract; const Parse = require('./parse'); -const Writer = require('fstream').Writer; +const fs = require('fs-extra'); const path = require('path'); const stream = require('stream'); const duplexer2 = require('duplexer2'); @@ -27,11 +27,13 @@ function Extract (opts) { return cb(); } - const writer = opts.getWriter ? opts.getWriter({path: extractPath}) : Writer({ path: extractPath }); + // Ensure the file and its parent directories exist + fs.ensureFile(extractPath).then(()=>{ - entry.pipe(writer) - .on('error', cb) - .on('close', cb); + const writer = opts.getWriter ? opts.getWriter({path: extractPath}) : fs.createWriteStream(extractPath); + + entry.pipe(writer).on('error', cb).on('close', cb); + }); }; const extract = duplexer2(parser, outStream); diff --git a/package.json b/package.json index 071a492..b6eec72 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ { "name": "Joe Ferner", "email": "joe.ferner@nearinfinity.com" + }, + { + "name": "Ayush Aher", + "email": "ayushaher118@gmail.com" } ], "repository": { @@ -23,22 +27,22 @@ }, "license": "MIT", "dependencies": { + "axios": "^1.7.2", "big-integer": "^1.6.17", "bluebird": "~3.4.1", "duplexer2": "~0.1.4", - "fstream": "^1.0.12", + "fs-extra": "^11.2.0", "graceful-fs": "^4.2.2" }, "devDependencies": { "@eslint/js": "^9.2.0", - "aws-sdk": "^2.77.0", + "aws-sdk": "^2.1636.0", "dirdiff": ">= 0.0.1 < 1", "eslint": "^9.2.0", "globals": "^15.2.0", "iconv-lite": "^0.4.24", - "request": "^2.88.0", "stream-buffers": ">= 0.2.5 < 1", - "tap": "^12.7.0", + "tap": "^19.2.2", "temp": ">= 0.4.0 < 1" }, "directories": { diff --git a/test/extractFromUrl.js b/test/extractFromUrl.js index e8b4461..e98e66c 100644 --- a/test/extractFromUrl.js +++ b/test/extractFromUrl.js @@ -1,25 +1,41 @@ const test = require("tap").test; const fs = require("fs"); const unzip = require("../"); -const os = require("os"); -const request = require("request"); +const axios = require("axios"); +const path = require("path"); -test("extract zip from url", function (t) { - const extractPath = os.tmpdir() + "/node-unzip-extract-fromURL"; // Not using path resolve, cause it should be resolved in extract() function - unzip.Open.url( - request, - "https://github.com/h5bp/html5-boilerplate/releases/download/v7.3.0/html5-boilerplate_v7.3.0.zip" - ) - .then(function(d) { return d.extract({ path: extractPath }); }) - .then(function() { - const dirFiles = fs.readdirSync(extractPath); - const isPassing = - dirFiles.length > 10 && - dirFiles.indexOf("css") > -1 && - dirFiles.indexOf("index.html") > -1 && - dirFiles.indexOf("favicon.ico") > -1; +test("extract zip from url", async function (t) { + const extractPath = path.join("../node-unzip-extract-fromURL"); // Ensure path is constructed correctly + const url = + "https://github.com/h5bp/html5-boilerplate/releases/download/v7.3.0/html5-boilerplate_v7.3.0.zip"; - t.equal(isPassing, true); - t.end(); + try { + // Fetch the zip file + const response = await axios({ + method: "get", + url: url, + responseType: "arraybuffer", // Download the file as a buffer }); + + // Buffer the response data + const buffer = Buffer.from(response.data); + + // Extract the buffer + const directory = await unzip.Open.buffer(buffer); + await directory.extract({ path: extractPath }); + + // Check extracted files + const dirFiles = fs.readdirSync(extractPath); + const isPassing = + dirFiles.length > 10 && + dirFiles.indexOf("css") > -1 && + dirFiles.indexOf("index.html") > -1 && + dirFiles.indexOf("favicon.ico") > -1; + + t.equal(isPassing, true); + } catch (error) { + t.fail(error.message); + } finally { + t.end(); + } }); diff --git a/test/openUrl.js b/test/openUrl.js index ba4df72..9171cf0 100644 --- a/test/openUrl.js +++ b/test/openUrl.js @@ -2,10 +2,9 @@ const test = require('tap').test; const fs = require('fs'); const path = require('path'); const unzip = require('../'); -const request = require('request'); test("get content of a single file entry out of a 502 MB zip from web", function (t) { - return unzip.Open.url(request, 'https://github.com/twbs/bootstrap/releases/download/v4.0.0/bootstrap-4.0.0-dist.zip') + return unzip.Open.url('https://github.com/twbs/bootstrap/releases/download/v4.0.0/bootstrap-4.0.0-dist.zip') .then(function(d) { const file = d.files.filter(function(d) { return d.path === 'css/bootstrap-reboot.min.css';