Move file upload into SMTP send job.

- This adds params for the upload URL and local file path, so that the
actual upload can happen in the send job.
- This also moves the URL generation to the client side so that we can
  generate a valid URL before the upload (because the MIME rendering of
  the mail message happens earlier and we want to include the URL there)
This commit is contained in:
Franz Heinzmann (Frando)
2020-06-10 12:34:51 +02:00
parent 060492afe8
commit 7d2105dbc9
6 changed files with 133 additions and 50 deletions

View File

@@ -1,5 +1,4 @@
const p = require('path')
const crypto = require('crypto')
const express = require('express')
const fs = require('fs')
const { pipeline } = require('stream')
@@ -10,44 +9,65 @@ const config = {
path: process.env.UPLOAD_PATH || p.resolve('./uploads'),
port: process.env.PORT || 8080,
hostname: process.env.HOSTNAME || '0.0.0.0',
baseurl: process.env.BASE_URL || null
baseurl: process.env.BASE_URL
}
if (!config.baseurl) config.baseurl = `http://${config.hostname}:${config.port}/`
if (!config.baseurl.endsWith('/')) config.baseurl = config.baseurl + '/'
if (!fs.existsSync(config.path)) {
fs.mkdirSync(config.path, { recursive: true })
}
const baseUrl = config.baseurl || `http://${config.hostname}:${config.port}/`
app.use('/:filename', checkFilenameMiddleware)
app.put('/:filename', (req, res) => {
const uploadpath = req.uploadpath
const filename = req.params.filename
fs.stat(uploadpath, (err, stat) => {
if (err && err.code !== 'ENOENT') {
console.error('error', err.message)
return res.code(500).send('internal server error')
}
if (stat) return res.status(500).send('filename in use')
app.post('*', (req, res) => {
const filename = crypto.randomBytes(12).toString('hex')
const ws = fs.createWriteStream(p.join(config.path, filename))
pipeline(req, ws, err => {
if (err) console.error(err)
if (err) res.status(500).send(err.message)
const url = baseUrl + filename
console.log('file uploaded: ' + filename)
res.send(url)
const ws = fs.createWriteStream(uploadpath)
pipeline(req, ws, err => {
if (err) {
console.error('error', err.message)
return res.status(500).send('internal server error')
}
console.log('file uploaded: ' + uploadpath)
const url = config.baseurl + filename
res.end(url)
})
})
})
app.get('/:filename', (req, res) => {
const filepath = p.normalize(p.join(config.path, req.params.filename))
if (!filepath.startsWith(config.path)) {
return res.code(500).send('bad request')
}
const rs = fs.createReadStream(filepath)
const uploadpath = req.uploadpath
const rs = fs.createReadStream(uploadpath)
res.setHeader('content-type', 'application/octet-stream')
pipeline(rs, res, err => {
if (err) console.error(err)
if (err) res.status(500).send(err.message)
res.end()
if (err) console.error('error', err.message)
if (err) return res.status(500).send(err.message)
})
})
function checkFilenameMiddleware (req, res, next) {
const filename = req.params.filename
if (!filename) return res.status(500).send('missing filename')
if (!filename.match(/^[a-zA-Z0-9]{26,32}$/)) {
return res.status(500).send('illegal filename')
}
const uploadpath = p.normalize(p.join(config.path, req.params.filename))
if (!uploadpath.startsWith(config.path)) {
return res.code(500).send('bad request')
}
req.uploadpath = uploadpath
next()
}
app.listen(config.port, err => {
if (err) console.error(err)
else console.log('Listening on ' + baseUrl)
else console.log(`Listening on ${config.baseurl}`)
})