Merge pull request #266 from mozilla/sizelimit

abort uploads over maxfilesize
This commit is contained in:
Danny Coates 2017-07-21 09:48:06 -07:00 committed by GitHub
commit fbf906fe5b
11 changed files with 415 additions and 2438 deletions

View File

@ -1,5 +1,6 @@
const FileReceiver = require('./fileReceiver'); const FileReceiver = require('./fileReceiver');
const { notify } = require('./utils'); const { notify } = require('./utils');
const bytes = require('bytes');
const $ = require('jquery'); const $ = require('jquery');
require('jquery-circle-progress'); require('jquery-circle-progress');
@ -29,20 +30,7 @@ $(document).ready(function() {
// update progress bar // update progress bar
$('#dl-progress').circleProgress('value', percent); $('#dl-progress').circleProgress('value', percent);
$('.percent-number').html(`${Math.floor(percent * 100)}`); $('.percent-number').html(`${Math.floor(percent * 100)}`);
if (progress[1] < 1000000) { $('.progress-text').text(`${filename} (${bytes(progress[0], {decimalPlaces: 1, fixedDecimals: true})} of ${bytes(progress[1], {decimalPlaces: 1})})`);
$('.progress-text').html(
`${filename} (${(progress[0] / 1000).toFixed(1)}KB of
${(progress[1] / 1000).toFixed(1)}KB)`
);
} else if (progress[1] < 1000000000) {
$('.progress-text').html(
`${filename} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000).toFixed(1)}MB)`
);
} else {
$('.progress-text').html(
`${filename} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)`
);
}
//on complete //on complete
if (percent === 1) { if (percent === 1) {
fileReceiver.removeAllListeners('progress'); fileReceiver.removeAllListeners('progress');

View File

@ -118,15 +118,17 @@ class FileSender extends EventEmitter {
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.readyState === XMLHttpRequest.DONE) {
// uuid field and url field if (xhr.status === 200) {
const responseObj = JSON.parse(xhr.responseText); const responseObj = JSON.parse(xhr.responseText);
resolve({ return resolve({
url: responseObj.url, url: responseObj.url,
fileId: responseObj.id, fileId: responseObj.id,
secretKey: keydata.k, secretKey: keydata.k,
deleteToken: responseObj.delete deleteToken: responseObj.delete
}); });
} }
reject(xhr.status);
}
}; };
xhr.open('post', '/upload', true); xhr.open('post', '/upload', true);

View File

@ -1,5 +1,7 @@
/* global MAXFILESIZE */
const FileSender = require('./fileSender'); const FileSender = require('./fileSender');
const { notify, gcmCompliant } = require('./utils'); const { notify, gcmCompliant } = require('./utils');
const bytes = require('bytes');
const $ = require('jquery'); const $ = require('jquery');
require('jquery-circle-progress'); require('jquery-circle-progress');
@ -84,6 +86,10 @@ $(document).ready(function() {
file = event.target.files[0]; file = event.target.files[0];
} }
if (file.size > MAXFILESIZE) {
return document.l10n.formatValue('fileTooBig', {size: bytes(MAXFILESIZE)}).then(alert);
}
$('#page-one').attr('hidden', true); $('#page-one').attr('hidden', true);
$('#upload-error').attr('hidden', true); $('#upload-error').attr('hidden', true);
$('#upload-progress').removeAttr('hidden'); $('#upload-progress').removeAttr('hidden');
@ -108,19 +114,7 @@ $(document).ready(function() {
$('#ul-progress').circleProgress().on('circle-animation-end', function() { $('#ul-progress').circleProgress().on('circle-animation-end', function() {
$('.percent-number').html(`${Math.floor(percent * 100)}`); $('.percent-number').html(`${Math.floor(percent * 100)}`);
}); });
if (progress[1] < 1000000) { $('.progress-text').text(`${file.name} (${bytes(progress[0], {decimalPlaces: 1, fixedDecimals: true})} of ${bytes(progress[1], {decimalPlaces: 1})})`);
$('.progress-text').text(
`${file.name} (${(progress[0] / 1000).toFixed(1)}KB of ${(progress[1] / 1000).toFixed(1)}KB)`
);
} else if (progress[1] < 1000000000) {
$('.progress-text').text(
`${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000).toFixed(1)}MB)`
);
} else {
$('.progress-text').text(
`${file.name} (${(progress[0] / 1000000).toFixed(1)}MB of ${(progress[1] / 1000000000).toFixed(1)}GB)`
);
}
}); });
fileSender.on('loading', isStillLoading => { fileSender.on('loading', isStillLoading => {

2723
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,7 @@ errorPageHeader = Something went wrong!
errorPageMessage = There has been an error uploading the file. errorPageMessage = There has been an error uploading the file.
errorPageLink = Send another file errorPageLink = Send another file
fileTooBig = That file is too big to upload. It should be less than { $size }.
linkExpiredAlt.alt = Link expired linkExpiredAlt.alt = Link expired
expiredPageHeader = This link has expired or never existed in the first place! expiredPageHeader = This link has expired or never existed in the first place!

View File

@ -36,6 +36,11 @@ const conf = convict({
format: ['production', 'development', 'test'], format: ['production', 'development', 'test'],
default: 'development', default: 'development',
env: 'NODE_ENV' env: 'NODE_ENV'
},
max_file_size: {
format: Number,
default: (1024 * 1024 * 1024) * 2,
env: 'P2P_MAX_FILE_SIZE'
} }
}); });

View File

@ -62,7 +62,11 @@ app.use(
} }
}) })
); );
app.use(busboy()); app.use(busboy({
limits: {
fileSize: conf.max_file_size
}
}));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(express.static(STATIC_PATH)); app.use(express.static(STATIC_PATH));
app.use('/l20n', express.static(L20N)); app.use('/l20n', express.static(L20N));
@ -77,6 +81,7 @@ app.get('/jsconfig.js', (req, res) => {
res.render('jsconfig', { res.render('jsconfig', {
trackerId: conf.analytics_id, trackerId: conf.analytics_id,
dsn: conf.sentry_id, dsn: conf.sentry_id,
maxFileSize: conf.max_file_size,
layout: false layout: false
}); });
}); });
@ -227,6 +232,12 @@ app.post('/upload', (req, res, next) => {
delete: meta.delete, delete: meta.delete,
id: newId id: newId
}); });
},
err => {
if (err.message === 'limit') {
return res.sendStatus(413);
}
res.sendStatus(500);
}); });
}); });

View File

@ -129,20 +129,24 @@ function localGet(id) {
function localSet(newId, file, filename, meta) { function localSet(newId, file, filename, meta) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const fstream = fs.createWriteStream( const filepath = path.join(__dirname, '../static', newId);
path.join(__dirname, '../static', newId) const fstream = fs.createWriteStream(filepath);
);
file.pipe(fstream); file.pipe(fstream);
fstream.on('close', () => { file.on('limit', () => {
file.unpipe(fstream);
fstream.destroy(new Error('limit'));
});
fstream.on('finish', () => {
redis_client.hmset(newId, meta); redis_client.hmset(newId, meta);
redis_client.expire(newId, 86400000); redis_client.expire(newId, 86400000);
log.info('localSet:', 'Upload Finished of ' + newId); log.info('localSet:', 'Upload Finished of ' + newId);
resolve(meta.delete); resolve(meta.delete);
}); });
fstream.on('error', () => { fstream.on('error', err => {
log.error('localSet:', 'Failed upload of ' + newId); log.error('localSet:', 'Failed upload of ' + newId);
reject(); fs.unlinkSync(filepath);
reject(err);
}); });
}); });
} }
@ -211,21 +215,25 @@ function awsSet(newId, file, filename, meta) {
Key: newId, Key: newId,
Body: file Body: file
}; };
let hitLimit = false;
return new Promise((resolve, reject) => { const upload = s3.upload(params);
s3.upload(params, function(err, _data) { file.on('limit', () => {
if (err) { hitLimit = true;
log.info('awsUploadError:', err.stack); // an error occurred upload.abort();
reject(); });
} else { return upload.promise()
.then(() => {
redis_client.hmset(newId, meta); redis_client.hmset(newId, meta);
redis_client.expire(newId, 86400000); redis_client.expire(newId, 86400000);
log.info('awsUploadFinish', 'Upload Finished of ' + filename); log.info('awsUploadFinish', 'Upload Finished of ' + filename);
resolve(meta.delete); },
err => {
if (hitLimit) {
throw new Error('limit');
} else {
throw err;
} }
}); });
});
} }
function awsDelete(id, delete_token) { function awsDelete(id, delete_token) {

View File

@ -110,9 +110,9 @@ describe('Testing Set using aws', function() {
it('Should pass when the file is successfully uploaded', function() { it('Should pass when the file is successfully uploaded', function() {
const buf = Buffer.alloc(10); const buf = Buffer.alloc(10);
sinon.stub(crypto, 'randomBytes').returns(buf); sinon.stub(crypto, 'randomBytes').returns(buf);
s3Stub.upload.callsArgWith(1, null, {}); s3Stub.upload.returns({promise: () => Promise.resolve()});
return storage return storage
.set('123', {}, 'Filename.moz', {}) .set('123', {on: sinon.stub()}, 'Filename.moz', {})
.then(() => { .then(() => {
assert(expire.calledOnce); assert(expire.calledOnce);
assert(expire.calledWith('123', 86400000)); assert(expire.calledWith('123', 86400000));
@ -121,9 +121,9 @@ describe('Testing Set using aws', function() {
}); });
it('Should fail if there was an error during uploading', function() { it('Should fail if there was an error during uploading', function() {
s3Stub.upload.callsArgWith(1, new Error(), null); s3Stub.upload.returns({promise: () => Promise.reject()});
return storage return storage
.set('123', {}, 'Filename.moz', 'url.com') .set('123', {on: sinon.stub()}, 'Filename.moz', 'url.com')
.then(_reply => assert.fail()) .then(_reply => assert.fail())
.catch(err => assert(1)); .catch(err => assert(1));
}); });

View File

@ -117,12 +117,12 @@ describe('Testing Get from local filesystem', function() {
describe('Testing Set to local filesystem', function() { describe('Testing Set to local filesystem', function() {
it('Successfully writes the file to the local filesystem', function() { it('Successfully writes the file to the local filesystem', function() {
const stub = sinon.stub(); const stub = sinon.stub();
stub.withArgs('close', sinon.match.any).callsArgWithAsync(1); stub.withArgs('finish', sinon.match.any).callsArgWithAsync(1);
stub.withArgs('error', sinon.match.any).returns(1); stub.withArgs('error', sinon.match.any).returns(1);
fsStub.createWriteStream.returns({ on: stub }); fsStub.createWriteStream.returns({ on: stub });
return storage return storage
.set('test', { pipe: sinon.stub() }, 'Filename.moz', {}) .set('test', { pipe: sinon.stub(), on: sinon.stub() }, 'Filename.moz', {})
.then(() => { .then(() => {
assert(1); assert(1);
}) })

View File

@ -4,3 +4,4 @@ window.dsn = '{{{dsn}}}';
{{#if trackerId}} {{#if trackerId}}
window.trackerId = '{{{trackerId}}}'; window.trackerId = '{{{trackerId}}}';
{{/if}} {{/if}}
const MAXFILESIZE = {{{maxFileSize}}};