diff --git a/app/src/Download.vue b/app/src/Download.vue index 2670073..01777b7 100644 --- a/app/src/Download.vue +++ b/app/src/Download.vue @@ -20,10 +20,15 @@ | decrypt .panel.panel-primary(v-if='!needsPassword') .panel-heading - a.pull-right(style="color:#fff", @click="downloadAll", v-if="downloadsAvailable") - i.fa.fa-fw.fa-download - | Download ZIP - | Files + strong Files + div.pull-right(style="margin-top:-5px;") + span.btn-group + a.btn.btn-sm.btn-default(@click="downloadAll('zip')", title="Archive download is not resumeable!") + i.fa.fa-fw.fa-fw.fa-download + | zip + a.btn.btn-sm.btn-default(@click="downloadAll('tar.gz')", title="Archive download is not resumeable!") + i.fa.fa-fw.fa-fw.fa-download + | tar.gz .panel-body table.table.table-hover.table-striped(style='margin-bottom: 0') tbody @@ -70,7 +75,7 @@ computed: { downloadsAvailable: function() { - return this.files.some(f => !f.downloaded || f.metadata.retention !== 'one-time') + return this.files.length > 1 && this.files.some(f => !f.downloaded || f.metadata.retention !== 'one-time') } }, @@ -84,14 +89,14 @@ file.downloaded = true; }, - downloadAll() { + downloadAll(format) { document.location.href = document.location.protocol + '//' + document.location.host + '/files/' + this.sid + '++' + MD5( - this.files - .filter(f => !f.downloaded || f.metadata.retention !== 'one-time') - .map(f => f.key).join() - ).toString() + '.zip'; + this.files + .filter(f => !f.downloaded || f.metadata.retention !== 'one-time') + .map(f => f.key).join() + ).toString() + '.' + format; this.files.forEach(f => { f.downloaded = true; diff --git a/lib/endpoints.js b/lib/endpoints.js index a1f6e83..66315f9 100644 --- a/lib/endpoints.js +++ b/lib/endpoints.js @@ -14,6 +14,7 @@ const AES = require("crypto-js/aes"); const MD5 = require("crypto-js/md5"); const debug = require('debug')('psitransfer:main'); const archiver = require('archiver'); +const zlib = require('zlib'); const errorPage = fs.readFileSync(path.join(__dirname, '../public/html/error.html')).toString(); const store = new Store(config.uploadDir); @@ -70,25 +71,28 @@ app.get('/:sid', (req, res, next) => { }); -// Download single file +// Download files app.get('/files/:fid', async(req, res, next) => { - // let tusboy handle HEAD with Tus Header + // let tusboy handle HEAD requests with Tus Header if(req.method === 'HEAD' && req.get('Tus-Resumable')) return next(); // Download all files - if(req.params.fid.endsWith('.zip')) { + if(req.params.fid.match(/^[a-z0-9+]+\.(tar\.gz|zip)$/)) { const sid = req.params.fid.split('++')[0]; + const format = req.params.fid.endsWith('.zip') ? 'zip' : 'tar.gz'; const bucket = db.get(sid); - if(req.params.fid !== sid + '++' + MD5(bucket.map(f => f.key).join()).toString() + '.zip') { + + if(req.params.fid !== sid + '++' + MD5(bucket.map(f => f.key).join()).toString() + '.' + format) { res.status(404).send(errorPage.replace('%%ERROR%%', 'Invalid link')); return; } debug(`Download Bucket ${sid}`); - res.header('ContentType', 'application/zip'); - res.header('Content-Disposition', 'attachment; filename="' + sid + '.zip"'); + if(format === 'zip') res.header('ContentType', 'application/zip'); + if(format === 'tar.gz') res.header('ContentType', 'application/x-gtar'); + res.header('Content-Disposition', `attachment; filename="${sid}.${format}"`); - const archive = archiver('zip'); + const archive = archiver(format === 'zip' ? 'zip' : 'tar'); archive.on('error', function(err) { console.error(err); }); @@ -100,7 +104,11 @@ app.get('/files/:fid', async(req, res, next) => { ); }); - archive.pipe(res); + if(format === 'tar.gz') { + archive.pipe(zlib.createGzip()).pipe(res); + } else { + archive.pipe(res); + } archive.finalize(); try {