diff --git a/src/apps.js b/src/apps.js index 55777d38f..8676536d2 100644 --- a/src/apps.js +++ b/src/apps.js @@ -2455,70 +2455,74 @@ async function listEventlog(app, page, perPage) { return await eventlog.listPaged(actions, search, page, perPage); } -function downloadFile(app, filePath, callback) { - assert.strictEqual(typeof app, 'object'); - assert.strictEqual(typeof filePath, 'string'); - assert.strictEqual(typeof callback, 'function'); - - exec(app, { cmd: [ 'stat', '--printf=%F-%s', filePath ], tty: true }, function (error, stream) { - if (error) return callback(error); - - var data = ''; +async function drainStream(stream) { + return new Promise((resolve, reject) => { + let data = ''; stream.setEncoding('utf8'); + stream.on('error', (error) => reject(new BoxError.FS_ERROR, error.message)); stream.on('data', function (d) { data += d; }); stream.on('end', function () { - var parts = data.split('-'); - if (parts.length !== 2) return callback(new BoxError(BoxError.NOT_FOUND, 'file does not exist')); - - var type = parts[0], filename, cmd, size; - - if (type === 'regular file') { - cmd = [ 'cat', filePath ]; - size = parseInt(parts[1], 10); - filename = path.basename(filePath); - if (isNaN(size)) return callback(new BoxError(BoxError.NOT_FOUND, 'file does not exist')); - } else if (type === 'directory') { - cmd = ['tar', 'zcf', '-', '-C', filePath, '.']; - filename = path.basename(filePath) + '.tar.gz'; - size = 0; // unknown - } else { - return callback(new BoxError(BoxError.NOT_FOUND, 'only files or dirs can be downloaded')); - } - - exec(app, { cmd: cmd , tty: false }, function (error, stream) { - if (error) return callback(error); - - var stdoutStream = new TransformStream({ - transform: function (chunk, ignoredEncoding, callback) { - this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk; - - for (;;) { - if (this._buffer.length < 8) break; // header is 8 bytes - - var type = this._buffer.readUInt8(0); - var len = this._buffer.readUInt32BE(4); - - if (this._buffer.length < (8 + len)) break; // not enough - - var payload = this._buffer.slice(8, 8 + len); - - this._buffer = this._buffer.slice(8+len); // consumed - - if (type === 1) this.push(payload); - } - - callback(); - } - }); - - stream.pipe(stdoutStream); - - callback(null, stdoutStream, { filename: filename, size: size }); - }); + resolve(data); }); }); } +async function downloadFile(app, filePath) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof filePath, 'string'); + + const statStream = await exec(app, { cmd: [ 'stat', '--printf=%F-%s', filePath ], tty: true }); + const data = await drainStream(statStream); + + const parts = data.split('-'); + if (parts.length !== 2) throw new BoxError(BoxError.NOT_FOUND, 'file does not exist'); + + let type = parts[0], filename, cmd, size; + + if (type === 'regular file') { + cmd = [ 'cat', filePath ]; + size = parseInt(parts[1], 10); + filename = path.basename(filePath); + if (isNaN(size)) throw new BoxError(BoxError.NOT_FOUND, 'file does not exist'); + } else if (type === 'directory') { + cmd = ['tar', 'zcf', '-', '-C', filePath, '.']; + filename = path.basename(filePath) + '.tar.gz'; + size = 0; // unknown + } else { + throw new BoxError(BoxError.NOT_FOUND, 'only files or dirs can be downloaded'); + } + + const inputStream = await exec(app, { cmd, tty: false }); + + // transforms the docker stream into a normal stream + const stdoutStream = new TransformStream({ + transform: function (chunk, ignoredEncoding, callback) { + this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk; + + for (;;) { + if (this._buffer.length < 8) break; // header is 8 bytes + + var type = this._buffer.readUInt8(0); + var len = this._buffer.readUInt32BE(4); + + if (this._buffer.length < (8 + len)) break; // not enough + + var payload = this._buffer.slice(8, 8 + len); + + this._buffer = this._buffer.slice(8+len); // consumed + + if (type === 1) this.push(payload); + } + + callback(); + } + }); + + inputStream.pipe(stdoutStream); + + return { stream: stdoutStream, filename, size }; +} + async function uploadFile(app, sourceFilePath, destFilePath) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof sourceFilePath, 'string'); diff --git a/src/routes/apps.js b/src/routes/apps.js index caf40df53..ffd82672b 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -798,26 +798,26 @@ async function uploadFile(req, res, next) { next(new HttpSuccess(202, {})); } -function downloadFile(req, res, next) { +async function downloadFile(req, res, next) { assert.strictEqual(typeof req.app, 'object'); if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided')); req.clearTimeout(); - apps.downloadFile(req.app, req.query.file, function (error, stream, info) { - if (error) return next(BoxError.toHttpError(error)); + const [error, result] = await safe(apps.downloadFile(req.app, req.query.file)); + if (error) return next(BoxError.toHttpError(error)); - var headers = { - 'Content-Type': 'application/octet-stream', - 'Content-Disposition': `attachment; filename*=utf-8''${encodeURIComponent(info.filename)}` // RFC 2184 section 4 - }; - if (info.size) headers['Content-Length'] = info.size; + const { stream, filename, size } = result; + const headers = { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': `attachment; filename*=utf-8''${encodeURIComponent(filename)}` // RFC 2184 section 4 + }; + if (size) headers['Content-Length'] = size; - res.writeHead(200, headers); + res.writeHead(200, headers); - stream.pipe(res); - }); + stream.pipe(res); } async function setMounts(req, res, next) {