import/export: Make sure Express sees async errors

Express v4.x does not check to see if a Promise returned from a
middleware function will be rejected, so explicitly pass the Promise
rejection reason to `next()`.

We can revert this change after we upgrade to Express v5.0.

See https://expressjs.com/en/guide/error-handling.html for details.
pull/4779/head
Richard Hansen 2021-02-07 19:56:07 -05:00 committed by John McLear
parent f59e0993a6
commit 48205c1ddb
1 changed files with 49 additions and 45 deletions

View File

@ -21,58 +21,62 @@ const limiter = rateLimit(settings.importExportRateLimiting);
exports.expressCreateServer = (hookName, args, cb) => { exports.expressCreateServer = (hookName, args, cb) => {
// handle export requests // handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter); args.app.use('/p/:pad/:rev?/export/:type', limiter);
args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => { args.app.get('/p/:pad/:rev?/export/:type', (req, res, next) => {
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad']; (async () => {
// send a 404 if we don't support this filetype const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
if (types.indexOf(req.params.type) === -1) { // send a 404 if we don't support this filetype
return next(); if (types.indexOf(req.params.type) === -1) {
}
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() === 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured');
// ACHTUNG: do not include req.params.type in res.send() because there is
// no HTML escaping and it would lead to an XSS
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' +
' or soffice (LibreOffice) in settings.json to enable this feature');
return;
}
res.header('Access-Control-Allow-Origin', '*');
if (await hasPadAccess(req, res)) {
let padId = req.params.pad;
let readOnlyId = null;
if (readOnlyManager.isReadOnlyId(padId)) {
readOnlyId = padId;
padId = await readOnlyManager.getPadId(readOnlyId);
}
const exists = await padManager.doesPadExists(padId);
if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next(); return next();
} }
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`); // if abiword is disabled, and this is a format we only support with abiword, output a message
exportHandler.doExport(req, res, padId, readOnlyId, req.params.type); if (settings.exportAvailable() === 'no' &&
} ['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
' There is no converter configured');
// ACHTUNG: do not include req.params.type in res.send() because there is
// no HTML escaping and it would lead to an XSS
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' +
' or soffice (LibreOffice) in settings.json to enable this feature');
return;
}
res.header('Access-Control-Allow-Origin', '*');
if (await hasPadAccess(req, res)) {
let padId = req.params.pad;
let readOnlyId = null;
if (readOnlyManager.isReadOnlyId(padId)) {
readOnlyId = padId;
padId = await readOnlyManager.getPadId(readOnlyId);
}
const exists = await padManager.doesPadExists(padId);
if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next();
}
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
}
})().catch((err) => next(err || new Error(err)));
}); });
// handle import requests // handle import requests
args.app.use('/p/:pad/import', limiter); args.app.use('/p/:pad/import', limiter);
args.app.post('/p/:pad/import', async (req, res, next) => { args.app.post('/p/:pad/import', (req, res, next) => {
const {session: {user} = {}} = req; (async () => {
const {accessStatus} = await securityManager.checkAccess( const {session: {user} = {}} = req;
req.params.pad, req.cookies.sessionID, req.cookies.token, user); const {accessStatus} = await securityManager.checkAccess(
if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) { req.params.pad, req.cookies.sessionID, req.cookies.token, user);
return res.status(403).send('Forbidden'); if (accessStatus !== 'grant' || !webaccess.userCanModify(req.params.pad, req)) {
} return res.status(403).send('Forbidden');
await importHandler.doImport(req, res, req.params.pad); }
await importHandler.doImport(req, res, req.params.pad);
})().catch((err) => next(err || new Error(err)));
}); });
return cb(); return cb();