mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-02-07 13:59:01 +00:00
refactor: Client/ServerError on uploadController
This commit is contained in:
parent
a37057e099
commit
b5af733dc2
@ -8,7 +8,10 @@ const searchQuery = require('search-query-parser')
|
||||
const paths = require('./pathsController')
|
||||
const perms = require('./permissionController')
|
||||
const utils = require('./utilsController')
|
||||
const apiErrorsHandler = require('./handlers/apiErrorsHandler.js')
|
||||
const ClientError = require('./utils/ClientError')
|
||||
const multerStorage = require('./utils/multerStorage')
|
||||
const ServerError = require('./utils/ServerError')
|
||||
const config = require('./../config')
|
||||
const logger = require('./../logger')
|
||||
const db = require('knex')(config.database)
|
||||
@ -110,7 +113,7 @@ const executeMulter = multer({
|
||||
fileFilter (req, file, cb) {
|
||||
file.extname = utils.extname(file.originalname)
|
||||
if (self.isExtensionFiltered(file.extname)) {
|
||||
return cb(`${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
|
||||
return cb(new ClientError(`${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`))
|
||||
}
|
||||
|
||||
// Re-map Dropzone keys so people can manually use the API without prepending 'dz'
|
||||
@ -121,7 +124,7 @@ const executeMulter = multer({
|
||||
}
|
||||
|
||||
if (req.body.chunkindex !== undefined && !chunkedUploads) {
|
||||
return cb('Chunked uploads are disabled at the moment.')
|
||||
return cb(new ClientError('Chunked uploads are disabled at the moment.'))
|
||||
} else {
|
||||
return cb(null, true)
|
||||
}
|
||||
@ -139,7 +142,7 @@ const executeMulter = multer({
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(error)
|
||||
return cb('Could not process the chunked upload. Try again?')
|
||||
return cb(new ServerError('Could not process the chunked upload. Try again?'))
|
||||
})
|
||||
} else {
|
||||
return cb(null, paths.uploads)
|
||||
@ -219,14 +222,14 @@ self.getUniqueRandomName = async (length, extension) => {
|
||||
logger.log(`${name} is already in use (${i + 1}/${utils.idMaxTries}).`)
|
||||
continue
|
||||
} catch (error) {
|
||||
// Re-throw error
|
||||
// Re-throw non-ENOENT error
|
||||
if (error & error.code !== 'ENOENT') throw error
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
throw 'Sorry, we could not allocate a unique random name. Try again?'
|
||||
throw new ServerError('Failed to allocate a unique name for the upload. Try again?')
|
||||
}
|
||||
|
||||
self.parseUploadAge = age => {
|
||||
@ -253,6 +256,7 @@ self.parseStripTags = stripTags => {
|
||||
}
|
||||
|
||||
self.upload = async (req, res, next) => {
|
||||
try {
|
||||
let user
|
||||
if (config.private === true) {
|
||||
user = await utils.authorize(req, res)
|
||||
@ -262,7 +266,7 @@ self.upload = async (req, res, next) => {
|
||||
.where('token', req.headers.token)
|
||||
.first()
|
||||
if (user && (user.enabled === false || user.enabled === 0)) {
|
||||
return res.json({ success: false, description: 'This account has been disabled.' })
|
||||
throw new ClientError('This account has been disabled.', { statusCode: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,20 +277,14 @@ self.upload = async (req, res, next) => {
|
||||
if (temporaryUploads) {
|
||||
age = self.parseUploadAge(req.headers.age)
|
||||
if (!age && !config.uploads.temporaryUploadAges.includes(0)) {
|
||||
return res.json({ success: false, description: 'Permanent uploads are not permitted.' })
|
||||
throw new ClientError('Permanent uploads are not permitted.', { statusCode: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const func = req.body.urls ? self.actuallyUploadUrls : self.actuallyUploadFiles
|
||||
await func(req, res, user, albumid, age)
|
||||
} catch (error) {
|
||||
const isError = error instanceof Error
|
||||
if (isError) logger.error(error)
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: isError ? error.toString() : error
|
||||
})
|
||||
return apiErrorsHandler(error, req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,14 +299,14 @@ self.actuallyUploadFiles = async (req, res, user, albumid, age) => {
|
||||
'LIMIT_UNEXPECTED_FILE'
|
||||
]
|
||||
if (suppress.includes(error.code)) {
|
||||
throw error.toString()
|
||||
throw new ClientError(error.toString())
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.files || !req.files.length) {
|
||||
throw 'No files.'
|
||||
throw new ClientError('No files.')
|
||||
}
|
||||
|
||||
// If chunked uploads is enabled and the uploaded file is a chunk, then just say that it was a success
|
||||
@ -336,32 +334,32 @@ self.actuallyUploadFiles = async (req, res, user, albumid, age) => {
|
||||
utils.unlinkFile(info.data.filename).catch(logger.error)
|
||||
))
|
||||
|
||||
throw 'Empty files are not allowed.'
|
||||
throw new ClientError('Empty files are not allowed.')
|
||||
}
|
||||
|
||||
if (utils.clamscan.instance) {
|
||||
const scanResult = await self.scanFiles(req, user, infoMap)
|
||||
if (scanResult) throw scanResult
|
||||
if (scanResult) throw new ClientError(scanResult)
|
||||
}
|
||||
|
||||
await self.stripTags(req, infoMap)
|
||||
|
||||
const result = await self.storeFilesToDb(req, res, user, infoMap)
|
||||
await self.sendUploadResponse(req, res, user, result)
|
||||
return self.sendUploadResponse(req, res, user, result)
|
||||
}
|
||||
|
||||
self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
if (!config.uploads.urlMaxSize) {
|
||||
throw 'Upload by URLs is disabled at the moment.'
|
||||
throw new ClientError('Upload by URLs is disabled at the moment.', { statusCode: 403 })
|
||||
}
|
||||
|
||||
const urls = req.body.urls
|
||||
if (!urls || !(urls instanceof Array)) {
|
||||
throw 'Missing "urls" property (array).'
|
||||
throw new ClientError('Missing "urls" property (array).')
|
||||
}
|
||||
|
||||
if (urls.length > maxFilesPerUpload) {
|
||||
throw `Maximum ${maxFilesPerUpload} URLs at a time.`
|
||||
throw new ClientError(`Maximum ${maxFilesPerUpload} URLs at a time.`)
|
||||
}
|
||||
|
||||
const downloaded = []
|
||||
@ -373,20 +371,16 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
|
||||
// Extensions filter
|
||||
let filtered = false
|
||||
if (['blacklist', 'whitelist'].includes(config.uploads.urlExtensionsFilterMode)) {
|
||||
if (urlExtensionsFilter) {
|
||||
if (urlExtensionsFilter && ['blacklist', 'whitelist'].includes(config.uploads.urlExtensionsFilterMode)) {
|
||||
const match = config.uploads.urlExtensionsFilter.some(extension => extname === extension.toLowerCase())
|
||||
const whitelist = config.uploads.urlExtensionsFilterMode === 'whitelist'
|
||||
filtered = ((!whitelist && match) || (whitelist && !match))
|
||||
} else {
|
||||
throw 'Invalid extensions filter, please contact the site owner.'
|
||||
}
|
||||
} else {
|
||||
filtered = self.isExtensionFiltered(extname)
|
||||
}
|
||||
|
||||
if (filtered) {
|
||||
throw `${extname ? `${extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`
|
||||
throw new ClientError(`${extname ? `${extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
|
||||
}
|
||||
|
||||
if (config.uploads.urlProxy) {
|
||||
@ -425,7 +419,7 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
}))
|
||||
|
||||
if (fetchFile.status !== 200) {
|
||||
throw `${fetchFile.status} ${fetchFile.statusText}`
|
||||
throw new ServerError(`${fetchFile.status} ${fetchFile.statusText}`)
|
||||
}
|
||||
|
||||
infoMap.push({
|
||||
@ -448,7 +442,7 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
|
||||
if (utils.clamscan.instance) {
|
||||
const scanResult = await self.scanFiles(req, user, infoMap)
|
||||
if (scanResult) throw scanResult
|
||||
if (scanResult) throw new ClientError(scanResult)
|
||||
}
|
||||
|
||||
const result = await self.storeFilesToDb(req, res, user, infoMap)
|
||||
@ -466,17 +460,18 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
const suppress = [
|
||||
/ over limit:/
|
||||
]
|
||||
if (!suppress.some(t => t.test(errorString))) {
|
||||
throw error
|
||||
if (suppress.some(t => t.test(errorString))) {
|
||||
throw new ClientError(errorString)
|
||||
} else {
|
||||
throw errorString
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finishChunks = async (req, res, next) => {
|
||||
try {
|
||||
if (!chunkedUploads) {
|
||||
return res.json({ success: false, description: 'Chunked upload is disabled at the moment.' })
|
||||
throw new ClientError('Chunked upload is disabled.', { statusCode: 403 })
|
||||
}
|
||||
|
||||
let user
|
||||
@ -488,19 +483,13 @@ self.finishChunks = async (req, res, next) => {
|
||||
.where('token', req.headers.token)
|
||||
.first()
|
||||
if (user && (user.enabled === false || user.enabled === 0)) {
|
||||
return res.json({ success: false, description: 'This account has been disabled.' })
|
||||
throw new ClientError('This account has been disabled.', { statusCode: 403 })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await self.actuallyFinishChunks(req, res, user)
|
||||
} catch (error) {
|
||||
const isError = error instanceof Error
|
||||
if (isError) logger.error(error)
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: isError ? error.toString() : error
|
||||
})
|
||||
return apiErrorsHandler(error, req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,7 +500,7 @@ self.actuallyFinishChunks = async (req, res, user) => {
|
||||
|
||||
const files = req.body.files
|
||||
if (!Array.isArray(files) || !files.length || files.some(check)) {
|
||||
throw 'An unexpected error occurred.'
|
||||
throw new ClientError('Bad request.')
|
||||
}
|
||||
|
||||
const infoMap = []
|
||||
@ -521,33 +510,33 @@ self.actuallyFinishChunks = async (req, res, user) => {
|
||||
chunksData[file.uuid].stream.end()
|
||||
|
||||
if (chunksData[file.uuid].chunks > maxChunksCount) {
|
||||
throw 'Too many chunks.'
|
||||
throw new ClientError('Too many chunks.')
|
||||
}
|
||||
|
||||
file.extname = typeof file.original === 'string' ? utils.extname(file.original) : ''
|
||||
if (self.isExtensionFiltered(file.extname)) {
|
||||
throw `${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`
|
||||
throw new ClientError(`${file.extname ? `${file.extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted.`)
|
||||
}
|
||||
|
||||
if (temporaryUploads) {
|
||||
file.age = self.parseUploadAge(file.age)
|
||||
if (!file.age && !config.uploads.temporaryUploadAges.includes(0)) {
|
||||
throw 'Permanent uploads are not permitted.'
|
||||
throw new ClientError('Permanent uploads are not permitted.')
|
||||
}
|
||||
}
|
||||
|
||||
file.size = chunksData[file.uuid].stream.bytesWritten
|
||||
if (config.filterEmptyFile && file.size === 0) {
|
||||
throw 'Empty files are not allowed.'
|
||||
throw new ClientError('Empty files are not allowed.')
|
||||
} else if (file.size > maxSizeBytes) {
|
||||
throw `File too large. Chunks are bigger than ${maxSize} MB.`
|
||||
throw new ClientError(`File too large. Chunks are bigger than ${maxSize} MB.`)
|
||||
}
|
||||
|
||||
// Double-check file size
|
||||
const tmpfile = path.join(chunksData[file.uuid].root, chunksData[file.uuid].filename)
|
||||
const lstat = await paths.lstat(tmpfile)
|
||||
if (lstat.size !== file.size) {
|
||||
throw `File size mismatched (${lstat.size} vs. ${file.size}).`
|
||||
throw new ClientError(`File size mismatched (${lstat.size} vs. ${file.size}).`)
|
||||
}
|
||||
|
||||
// Generate name
|
||||
@ -586,7 +575,7 @@ self.actuallyFinishChunks = async (req, res, user) => {
|
||||
|
||||
if (utils.clamscan.instance) {
|
||||
const scanResult = await self.scanFiles(req, user, infoMap)
|
||||
if (scanResult) throw scanResult
|
||||
if (scanResult) throw new ClientError(scanResult)
|
||||
}
|
||||
|
||||
await self.stripTags(req, infoMap)
|
||||
@ -615,6 +604,7 @@ self.cleanUpChunks = async (uuid, onTimeout) => {
|
||||
// Remove tmp file
|
||||
await paths.unlink(path.join(chunksData[uuid].root, chunksData[uuid].filename))
|
||||
.catch(error => {
|
||||
// Re-throw non-ENOENT error
|
||||
if (error.code !== 'ENOENT') logger.error(error)
|
||||
})
|
||||
|
||||
@ -799,7 +789,7 @@ self.storeFilesToDb = async (req, res, user, infoMap) => {
|
||||
|
||||
self.sendUploadResponse = async (req, res, user, result) => {
|
||||
// Send response
|
||||
res.json({
|
||||
return res.json({
|
||||
success: true,
|
||||
files: result.map(file => {
|
||||
const map = {
|
||||
@ -832,7 +822,7 @@ self.sendUploadResponse = async (req, res, user, result) => {
|
||||
})
|
||||
}
|
||||
|
||||
self.delete = async (req, res) => {
|
||||
self.delete = async (req, res, next) => {
|
||||
// Map /api/delete requests to /api/bulkdelete
|
||||
let body
|
||||
if (req.method === 'POST') {
|
||||
@ -852,10 +842,11 @@ self.delete = async (req, res) => {
|
||||
} */
|
||||
|
||||
req.body = body
|
||||
return self.bulkDelete(req, res)
|
||||
return self.bulkDelete(req, res, next)
|
||||
}
|
||||
|
||||
self.bulkDelete = async (req, res) => {
|
||||
self.bulkDelete = async (req, res, next) => {
|
||||
try {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
@ -863,19 +854,18 @@ self.bulkDelete = async (req, res) => {
|
||||
const values = req.body.values
|
||||
|
||||
if (!Array.isArray(values) || !values.length) {
|
||||
return res.json({ success: false, description: 'No array of files specified.' })
|
||||
throw new ClientError('No array of files specified.')
|
||||
}
|
||||
|
||||
try {
|
||||
const failed = await utils.bulkDeleteFromDb(field, values, user)
|
||||
return res.json({ success: true, failed })
|
||||
await res.json({ success: true, failed })
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
return apiErrorsHandler(error, req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
self.list = async (req, res) => {
|
||||
self.list = async (req, res, next) => {
|
||||
try {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
@ -994,20 +984,14 @@ self.list = async (req, res) => {
|
||||
|
||||
// Regular user threshold check
|
||||
if (!ismoderator && textQueries > MAX_TEXT_QUERIES) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Users are only allowed to use ${MAX_TEXT_QUERIES} non-keyed keyword${MAX_TEXT_QUERIES === 1 ? '' : 's'} at a time.`
|
||||
})
|
||||
throw new ClientError(`Users are only allowed to use ${MAX_TEXT_QUERIES} non-keyed keyword${MAX_TEXT_QUERIES === 1 ? '' : 's'} at a time.`)
|
||||
}
|
||||
|
||||
if (filterObj.queries.text) {
|
||||
for (let i = 0; i < filterObj.queries.text.length; i++) {
|
||||
const result = sqlLikeParser(filterObj.queries.text[i])
|
||||
if (!ismoderator && result.count > MAX_WILDCARDS_IN_KEY) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Users are only allowed to use ${MAX_WILDCARDS_IN_KEY} wildcard${MAX_WILDCARDS_IN_KEY === 1 ? '' : 's'} per key.`
|
||||
})
|
||||
throw new ClientError(`Users are only allowed to use ${MAX_WILDCARDS_IN_KEY} wildcard${MAX_WILDCARDS_IN_KEY === 1 ? '' : 's'} per key.`)
|
||||
}
|
||||
filterObj.queries.text[i] = result.escaped
|
||||
}
|
||||
@ -1017,10 +1001,7 @@ self.list = async (req, res) => {
|
||||
for (let i = 0; i < filterObj.queries.exclude.text.length; i++) {
|
||||
const result = sqlLikeParser(filterObj.queries.exclude.text[i])
|
||||
if (!ismoderator && result.count > MAX_WILDCARDS_IN_KEY) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Users are only allowed to use ${MAX_WILDCARDS_IN_KEY} wildcard${MAX_WILDCARDS_IN_KEY === 1 ? '' : 's'} per key.`
|
||||
})
|
||||
throw new ClientError(`Users are only allowed to use ${MAX_WILDCARDS_IN_KEY} wildcard${MAX_WILDCARDS_IN_KEY === 1 ? '' : 's'} per key.`)
|
||||
}
|
||||
filterObj.queries.exclude.text[i] = result.escaped
|
||||
}
|
||||
@ -1135,10 +1116,7 @@ self.list = async (req, res) => {
|
||||
return !uploaders.find(uploader => uploader.username === username)
|
||||
})
|
||||
if (notFound) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `User${notFound.length === 1 ? '' : 's'} not found: ${notFound.join(', ')}.`
|
||||
})
|
||||
throw new ClientError(`User${notFound.length === 1 ? '' : 's'} not found: ${notFound.join(', ')}.`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1178,10 +1156,7 @@ self.list = async (req, res) => {
|
||||
|
||||
if (!allowed.includes(column)) {
|
||||
// Alert users if using disallowed/missing columns
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Column \`${column}\` cannot be used for sorting.\n\nTry the following instead:\n${allowed.join(', ')}`
|
||||
})
|
||||
throw new ClientError(`Column "${column}" cannot be used for sorting.\n\nTry the following instead:\n${allowed.join(', ')}`)
|
||||
}
|
||||
|
||||
sortObj.parsed.push({
|
||||
@ -1194,10 +1169,7 @@ self.list = async (req, res) => {
|
||||
|
||||
// Regular user threshold check
|
||||
if (!ismoderator && sortObj.parsed.length > MAX_SORT_KEYS) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Users are only allowed to use ${MAX_SORT_KEYS} sort key${MAX_SORT_KEYS === 1 ? '' : 's'} at a time.`
|
||||
})
|
||||
throw new ClientError(`Users are only allowed to use ${MAX_SORT_KEYS} sort key${MAX_SORT_KEYS === 1 ? '' : 's'} at a time.`)
|
||||
}
|
||||
|
||||
// Delete key to avoid unexpected behavior
|
||||
@ -1216,10 +1188,7 @@ self.list = async (req, res) => {
|
||||
if (inQuery || inExclude) {
|
||||
filterObj.flags[`is${type}`] = inExclude ? false : inQuery
|
||||
if (isLast !== undefined && isLast !== filterObj.flags[`is${type}`]) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: 'Cannot mix inclusion and exclusion type-is keys.'
|
||||
})
|
||||
throw new ClientError('Cannot mix inclusion and exclusion type-is keys.')
|
||||
}
|
||||
isKeys++
|
||||
isLast = filterObj.flags[`is${type}`]
|
||||
@ -1233,10 +1202,7 @@ self.list = async (req, res) => {
|
||||
|
||||
// Regular user threshold check
|
||||
if (!ismoderator && isKeys > MAX_IS_KEYS) {
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Users are only allowed to use ${MAX_IS_KEYS} type-is key${MAX_IS_KEYS === 1 ? '' : 's'} at a time.`
|
||||
})
|
||||
throw new ClientError(`Users are only allowed to use ${MAX_IS_KEYS} type-is key${MAX_IS_KEYS === 1 ? '' : 's'} at a time.`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1376,7 +1342,6 @@ self.list = async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Query uploads count for pagination
|
||||
const count = await db.table('files')
|
||||
.where(filter)
|
||||
@ -1477,10 +1442,9 @@ self.list = async (req, res) => {
|
||||
users[user.id] = user.username
|
||||
}
|
||||
|
||||
return res.json({ success: true, files, count, users, albums, basedomain })
|
||||
await res.json({ success: true, files, count, users, albums, basedomain })
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
return apiErrorsHandler(error, req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user