1
0
mirror of https://github.com/Foltik/Shimapan synced 2024-12-04 11:24:32 -05:00
shimapan/app/util/upload/multipart.js

108 lines
3.5 KiB
JavaScript

const Busboy = require('busboy');
const is = require('type-is');
const config = require('config');
const authenticate = require('../auth/authenticate');
const disk = require('./disk');
const identifier = require('./id');
const uploadMultipart = async (req, res, next) => {
if (!is(req, ['multipart']))
return res.status(400).json({message: 'Bad request.'});
// Store whether the user has authenticated, because an api key might be included with the form later
// If not authenticated with a session, we'll have to wait for key authentication from the multipart form data
let status = await authenticate(req, 'file.upload');
// Function to call once the file is sent or an error is encountered
let isDone = false;
const done = async (err, data) => {
if (isDone) return;
isDone = true;
req.unpipe(busboy);
busboy.removeAllListeners();
req.on('readable', req.read.bind(req));
if (err) {
if (data.path)
await disk.remove(data.path);
if (err === 'LIMIT_FILE_SIZE')
return res.status(413).json({message: 'File too large.'});
else {
console.log(err.stack);
return res.status(500).json({message: 'Internal server error.'});
}
} else {
req.file = data.file;
next();
}
};
// Create the busboy object to parse the multipart data
const busboy = new Busboy({
headers: req.headers,
limits: {
fileSize: config.get('Upload.maxSize')
}
});
req.body = {};
busboy.on('field', (fieldName, value) => {
req.body[fieldName] = value;
});
let fileReceived = false;
busboy.on('file', async (fieldName, stream, name, encoding, mime) => {
// Only process one file, discard everything after that
if (fileReceived)
return req.unpipe(busboy);
fileReceived = true;
// If a key was encountered and we are not authenticated, try to authenticate with it before the final check
if (req.body.key && !status.authenticated)
status = await authenticate(req, 'file.upload', status);
// Finally, check if we have auth before preceeding, keys should have been processed by now
if (!status.authenticated)
return res.status(401).json({message: 'Unauthorized.'});
if (!status.permission)
return res.status(403).json({message: 'Forbidden.'});
// Don't attach to the files object if there is no file
if (!name) return stream.resume();
// Generate an ID for the file, saving it with that filename
const path = config.get('Upload.path');
const newName = await identifier.generate();
const finalPath = path + '/' + newName;
// Set the file attributes
file = {
originalName: name,
name: newName,
path: finalPath,
encoding: encoding,
mime: mime
};
// Ensure the output directory exists
await disk.mkdir(path);
// Handle errors
stream.on('error', err => done(err, {path: finalPath}));
stream.on('limit', () => done('LIMIT_FILE_SIZE', {path: finalPath}));
file.size = await disk.write(finalPath, stream);
await done(null, {file: file});
});
busboy.on('error', err => done(err));
busboy.on('finished', () => done(null, {file: file}));
req.pipe(busboy);
};
module.exports = uploadMultipart;