|
- 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;
|