refactor: ServeStatic custom middleware (WIP)

currently when enabled will force-close lolisafe

i still need to find a decent backend library to make life easier
This commit is contained in:
Bobby Wibowo 2022-07-12 06:29:21 +07:00
parent 21d75f71f3
commit b0913eaf59
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
2 changed files with 138 additions and 93 deletions

View File

@ -0,0 +1,121 @@
const contentDisposition = require('content-disposition')
const SimpleDataStore = require('../utils/SimpleDataStore')
const paths = require('../pathsController')
const utils = require('../utilsController')
const logger = require('../../logger')
class ServeStatic {
directory
contentDispositionStore
contentTypesMaps
async #setContentDisposition () {}
#setContentType () {}
constructor (directory, options = {}) {
logger.error('new ServeStatic()')
if (!directory || typeof directory !== 'string') {
throw new TypeError('Root directory must be set')
}
this.directory = directory
// Init Content-Type overrides
if (typeof options.overrideContentTypes === 'object') {
this.contentTypesMaps = new Map()
const types = Object.keys(options.overrideContentTypes)
for (const type of types) {
const extensions = options.overrideContentTypes[type]
if (Array.isArray(extensions)) {
for (const extension of extensions) {
this.contentTypesMaps.set(extension, type)
}
}
}
if (this.contentTypesMaps.size) {
this.#setContentType = (res, path, stat) => {
// Do only if accessing files from uploads' root directory (i.e. not thumbs, etc.)
const relpath = path.replace(paths.uploads, '')
if (relpath.indexOf('/', 1) === -1) {
const name = relpath.substring(1)
const extname = utils.extname(name).substring(1)
const contentType = this.contentTypesMaps.get(extname)
if (contentType) {
res.set('Content-Type', contentType)
}
}
}
} else {
this.contentTypesMaps = undefined
}
}
// Init Content-Disposition store and setHeaders function if required
if (options.setContentDisposition) {
this.contentDispositionStore = new SimpleDataStore(
options.contentDispositionOptions || {
limit: 50,
strategy: SimpleDataStore.STRATEGIES[0]
}
)
this.#setContentDisposition = async (res, path, stat) => {
// Do only if accessing files from uploads' root directory (i.e. not thumbs, etc.)
const relpath = path.replace(paths.uploads, '')
if (relpath.indexOf('/', 1) !== -1) return
const name = relpath.substring(1)
try {
let original = this.contentDispositionStore.get(name)
if (original === undefined) {
this.contentDispositionStore.hold(name)
original = await utils.db.table('files')
.where('name', name)
.select('original')
.first()
.then(_file => {
this.contentDispositionStore.set(name, _file.original)
return _file.original
})
}
if (original) {
res.set('Content-Disposition', contentDisposition(original, { type: 'inline' }))
}
} catch (error) {
this.contentDispositionStore.delete(name)
logger.error(error)
}
}
logger.debug('Inititated SimpleDataStore for Content-Disposition: ' +
`{ limit: ${this.contentDispositionStore.limit}, strategy: "${this.contentDispositionStore.strategy}" }`)
}
}
async #setHeaders (req, res) {
logger.log('ServeStatic.setHeaders()')
this.#setContentType(req, res)
// Only set Content-Disposition on GET requests
if (req.method === 'GET') {
await this.#setContentDisposition(req, res)
}
}
async #middleware (req, res, next) {
logger.log(`ServeStatic.middleware(): ${this.directory}, ${req.path}`)
// TODO
return next()
}
get middleware () {
return this.#middleware.bind(this)
}
}
module.exports = ServeStatic

View File

@ -10,7 +10,6 @@ process.on('unhandledRejection', error => {
}) })
// Libraries // Libraries
const contentDisposition = require('content-disposition')
const helmet = require('helmet') const helmet = require('helmet')
const HyperExpress = require('hyper-express') const HyperExpress = require('hyper-express')
const LiveDirectory = require('live-directory') const LiveDirectory = require('live-directory')
@ -130,74 +129,6 @@ const cdnPages = [...config.pages]
// Defaults to no-op // Defaults to no-op
let setHeadersForStaticAssets = () => {} let setHeadersForStaticAssets = () => {}
const contentTypes = typeof config.overrideContentTypes === 'object' &&
Object.keys(config.overrideContentTypes)
const overrideContentTypes = contentTypes && contentTypes.length && function (res, path) {
// Do only if accessing files from uploads' root directory (i.e. not thumbs, etc.)
const relpath = path.replace(paths.uploads, '')
if (relpath.indexOf('/', 1) === -1) {
const name = relpath.substring(1)
const extname = utils.extname(name).substring(1)
for (const contentType of contentTypes) {
if (config.overrideContentTypes[contentType].includes(extname)) {
res.set('Content-Type', contentType)
break
}
}
}
}
const initServeStaticUploads = (opts = {}) => {
if (config.setContentDisposition) {
const SimpleDataStore = require('./controllers/utils/SimpleDataStore')
utils.contentDispositionStore = new SimpleDataStore(
config.contentDispositionOptions || {
limit: 50,
strategy: SimpleDataStore.STRATEGIES[0]
}
)
opts.preSetHeaders = async (res, req, path, stat) => {
// Do only if accessing files from uploads' root directory (i.e. not thumbs, etc.),
// AND only if GET requests
const relpath = path.replace(paths.uploads, '')
if (relpath.indexOf('/', 1) !== -1 || req.method !== 'GET') return
const name = relpath.substring(1)
try {
let original = utils.contentDispositionStore.get(name)
if (original === undefined) {
utils.contentDispositionStore.hold(name)
original = await utils.db.table('files')
.where('name', name)
.select('original')
.first()
.then(_file => {
utils.contentDispositionStore.set(name, _file.original)
return _file.original
})
}
if (original) {
res.set('Content-Disposition', contentDisposition(original, { type: 'inline' }))
}
} catch (error) {
utils.contentDispositionStore.delete(name)
logger.error(error)
}
}
// serveStatic is provided with @bobbywibowo/serve-static, a fork of express/serve-static.
// The fork allows specifying an async function by the name preSetHeaders,
// which it will await before creating 'send' stream to client.
// This is necessary due to database queries being async tasks,
// and express/serve-static not having the functionality by default.
// safe.use('/', require('@bobbywibowo/serve-static')(paths.uploads, opts))
// logger.debug('Inititated SimpleDataStore for Content-Disposition: ' +
// `{ limit: ${utils.contentDispositionStore.limit}, strategy: "${utils.contentDispositionStore.strategy}" }`)
logger.error('initServeStaticUploads() was called, but still WIP')
} else {
// safe.use('/', express.static(paths.uploads, opts))
logger.error('initServeStaticUploads() was called, but still WIP')
}
}
// Cache control // Cache control
if (config.cacheControl) { if (config.cacheControl) {
const cacheControls = { const cacheControls = {
@ -232,23 +163,6 @@ if (config.cacheControl) {
break break
} }
// If serving uploads with node
if (config.serveFilesWithNode) {
initServeStaticUploads({
setHeaders: (res, path) => {
// Override Content-Type header if necessary
if (overrideContentTypes) {
overrideContentTypes(res, path)
}
// If using CDN, cache uploads in CDN as well
// Use with cloudflare.purgeCache enabled in config file
if (config.cacheControl !== 2) {
res.set('Cache-Control', cacheControls.cdn)
}
}
})
}
// Function for static assets. // Function for static assets.
// This requires the assets to use version in their query string, // This requires the assets to use version in their query string,
// as they will be cached by clients for a very long time. // as they will be cached by clients for a very long time.
@ -266,13 +180,6 @@ if (config.cacheControl) {
} }
return next() return next()
}) })
} else if (config.serveFilesWithNode) {
const opts = {}
// Override Content-Type header if necessary
if (overrideContentTypes) {
opts.setHeaders = overrideContentTypes
}
initServeStaticUploads(opts)
} }
// Static assets // Static assets
@ -354,6 +261,23 @@ safe.use('/api', api)
} }
} }
// Init ServerStatic last if serving uploaded files with node
/* // TODO
if (config.serveFilesWithNode) {
const serveStaticInstance = new ServeStatic(paths.uploads, {
contentDispositionOptions: config.contentDispositionOptions,
overrideContentTypes: config.overrideContentTypes,
setContentDisposition: config.setContentDisposition
})
safe.use('/', serveStaticInstance.middleware)
utils.contentDispositionStore = serveStaticInstance.contentDispositionStore
}
*/
if (config.serveFilesWithNode) {
logger.error('Serving files with node is currently not available in this branch.')
return process.exit(1)
}
// Web server error handlers (must always be set after all routes/middlewares) // Web server error handlers (must always be set after all routes/middlewares)
safe.set_not_found_handler(errors.handleNotFound) safe.set_not_found_handler(errors.handleNotFound)
safe.set_error_handler(errors.handleError) safe.set_error_handler(errors.handleError)