quad-j/index.js
2020-10-25 13:42:09 +02:00

159 lines
No EOL
4.4 KiB
JavaScript

const config = require('./config');
const express = require('express');
const fileUpload = require('express-fileupload');
const path = require('path');
const fs = require('fs');
const mime = require('mime-types')
const cors = require('cors');
const app = express();
app.use(fileUpload());
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
const isPathValid = (filename, filePath) => {
if (!filename) {
return false;
}
if (filename.indexOf('\0') !== -1 || filename.indexOf('%') !== -1 || filename.indexOf('/') !== -1 || filename.indexOf('..') !== -1) {
return false;
}
if (!/[A-Za-z1-9.]+/g.test(filename)) {
return false;
}
if (filePath.indexOf(config.storagePath) !== 0) {
return false;
}
return true;
};
const isFilenameValid = (filename) => {
if (!filename) {
return false;
}
if (filename.indexOf('\0') !== -1 || filename.indexOf('%') !== -1 || filename.indexOf('..') !== -1 || filename.indexOf('&') !== -1) {
return false;
}
if (!/[A-Za-z1-9.]+/g.test(filename)) {
return false;
}
return true;
};
const getFileType = (filename) => {
const extension = path.extname(filename).substring(1);
let type = config.files.embed[extension];
if (!type) {
type = config.files.other;
}
return type;
};
const allowlist = ['https://hippoz.xyz/', 'https://hippoz.xyz']
const corsOptions = {
origin: function (origin, callback) {
if (allowlist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
app.get('/', (req, res) => {
if (!config.rootRedirect) {
res.render('upload');
} else {
res.redirect(301, config.rootRedirect);
}
});
app.get('/file/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(config.storagePath, filename);
const isValid = isPathValid(filename, filePath);
if (!isValid) {
res.status(400).send('invalid input.');
return;
}
fs.access(filePath, fs.F_OK, (err) => {
if (err) {
res.status(404).send('file not found or is invalid.');
return;
}
const type = getFileType(filePath);
const mimeType = mime.lookup(filePath);
if (type === config.files.other) {
res.contentType('text/plain');
} else {
res.contentType(mimeType);
}
res.sendFile(filePath);
});
});
// TODO: add cors for all endpoints
app.post('/api/upload', [ cors(corsOptions) ], (req, res) => {
const password = req.body.password;
const chosenFileName = req.body.filename;
if (config.passwords.indexOf(password) === -1) {
return res.status(401).render('uploadfailed', { message: 'the password you entered is not correct.' });
}
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).render('uploadfailed', { message: 'you forgot to upload a file.' });
}
const file = req.files.file;
if (!isFilenameValid(chosenFileName)) {
return res.status(400).render('uploadfailed', { message: 'invalid name.' });
}
const filepath = path.join(config.storagePath, chosenFileName);
if (!isFilenameValid(file.name) || !isPathValid(chosenFileName, filepath)) {
return res.status(400).render('uploadfailed', { message: 'invalid name.' });
}
fs.stat(filepath, (err) => {
if(err == null) {
return res.status(400).render('uploadfailed', { message: 'a file with that name already exists.' });
} else if(err.code === 'ENOENT') {
file.mv(filepath, (err) => {
if (err) return res.status(500).render('uploadfailed', { message: 'something went wrong while uploading the file.' });
res.render('uploaded', { baseurl: config.url, file: { name: chosenFileName } });
});
} else {
return res.status(500).render('uploadfailed', { message: 'something went wrong.' });;
}
});
});
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('internal server error: something went wrong');
});
app.listen(config.server.port, () => {
console.log(`started server on port ${config.server.port}`);
});