mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-19 01:31:34 +00:00
Updates [!! update config.js !!]
Added extended support for URL uploads. Namely URL proxy support and separate extensions filter (as in separate from the primary extensions filter). There's also a new option to set a disclaimer message that will be printed underneath the URL uploads form. Trust proxy is now toggleable from the configuration file. I think they should only be enabled when you're behind proxy such as Cloudflare or Incapsula. I'm not sure how it behaves with only a bare nginx reverse proxy though. Empty files can now be filtered. Sorted preset extensions filter in config.sample.js. Rephrased some options in config.sample.js as well. maxTries now default to 3 in config.sample.js. Various other small changes.
This commit is contained in:
parent
bba6836708
commit
d723c0f562
113
config.sample.js
113
config.sample.js
@ -30,7 +30,7 @@ module.exports = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
If you are serving your files with a different domain than your lolisafe homepage,
|
If you are serving your files with a different domain than your lolisafe homepage,
|
||||||
then fill this option with your lolisafe homepage, otherwise leave it null (or other falsy value).
|
then fill this option with your lolisafe homepage, otherwise any falsy value.
|
||||||
This will be used when listing album links in the dashboard.
|
This will be used when listing album links in the dashboard.
|
||||||
*/
|
*/
|
||||||
homeDomain: null,
|
homeDomain: null,
|
||||||
@ -46,30 +46,30 @@ module.exports = {
|
|||||||
pages: ['home', 'auth', 'dashboard', 'faq'],
|
pages: ['home', 'auth', 'dashboard', 'faq'],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If set to true, all extensions in "extensionsFilter" array will be blacklisted,
|
This can be either 'blacklist' or 'whitelist', which should be self-explanatory.
|
||||||
otherwise only files with those extensions that can be uploaded.
|
When this is set to neither, this will fallback to 'blacklist'.
|
||||||
*/
|
*/
|
||||||
filterBlacklist: true,
|
extensionsFilterMode: 'blacklist',
|
||||||
|
|
||||||
extensionsFilter: [
|
extensionsFilter: [
|
||||||
'.jar',
|
'.bash_profile',
|
||||||
|
'.bash',
|
||||||
|
'.bashrc',
|
||||||
|
'.bat',
|
||||||
|
'.bsh',
|
||||||
|
'.cmd',
|
||||||
|
'.com',
|
||||||
|
'.csh',
|
||||||
'.exe',
|
'.exe',
|
||||||
'.exec',
|
'.exec',
|
||||||
|
'.jar',
|
||||||
'.msi',
|
'.msi',
|
||||||
'.com',
|
|
||||||
'.bat',
|
|
||||||
'.cmd',
|
|
||||||
'.nt',
|
'.nt',
|
||||||
'.scr',
|
'.profile',
|
||||||
'.ps1',
|
'.ps1',
|
||||||
'.psm1',
|
'.psm1',
|
||||||
'.sh',
|
'.scr',
|
||||||
'.bash',
|
'.sh'
|
||||||
'.bsh',
|
|
||||||
'.csh',
|
|
||||||
'.bash_profile',
|
|
||||||
'.bashrc',
|
|
||||||
'.profile'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -77,6 +77,13 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
filterNoExtension: false,
|
filterNoExtension: false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
If set to true, files with zero bytes size will always be rejected.
|
||||||
|
NOTE: Even if the files only contain whitespaces, as long as they aren't
|
||||||
|
zero bytes, they will be accepted.
|
||||||
|
*/
|
||||||
|
filterEmptyFile: true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Show hash of the current git commit in homepage.
|
Show hash of the current git commit in homepage.
|
||||||
*/
|
*/
|
||||||
@ -84,7 +91,7 @@ module.exports = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Path to error pages. Only 404 and 500 will be used.
|
Path to error pages. Only 404 and 500 will be used.
|
||||||
Note: rootDir can either be relative or absolute path.
|
NOTE: rootDir can either be relative or absolute path.
|
||||||
*/
|
*/
|
||||||
errorPages: {
|
errorPages: {
|
||||||
rootDir: './pages/error',
|
rootDir: './pages/error',
|
||||||
@ -92,18 +99,24 @@ module.exports = {
|
|||||||
500: '500.html'
|
500: '500.html'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trust proxy.
|
||||||
|
Only enable if you are running this behind a proxy like Cloudflare, Incapsula, etc.
|
||||||
|
*/
|
||||||
|
trustProxy: true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Uploads config.
|
Uploads config.
|
||||||
*/
|
*/
|
||||||
uploads: {
|
uploads: {
|
||||||
/*
|
/*
|
||||||
Folder where images should be stored.
|
Folder where files should be stored.
|
||||||
*/
|
*/
|
||||||
folder: 'uploads',
|
folder: 'uploads',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Max file size allowed. Needs to be in MB.
|
Max file size allowed. Needs to be in MB.
|
||||||
Note: When maxSize is greater than 1 MiB and using nginx as reverse proxy,
|
NOTE: When maxSize is greater than 1 MiB and using nginx as reverse proxy,
|
||||||
you must set client_max_body_size to the same as maxSize.
|
you must set client_max_body_size to the same as maxSize.
|
||||||
https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||||
*/
|
*/
|
||||||
@ -111,10 +124,58 @@ module.exports = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Max file size allowed for upload by URLs. Needs to be in MB.
|
Max file size allowed for upload by URLs. Needs to be in MB.
|
||||||
NOTE: Set to falsy value (false, null, etc.) to disable upload by URLs.
|
NOTE: Set to falsy value to disable upload by URLs.
|
||||||
*/
|
*/
|
||||||
urlMaxSize: '32MB',
|
urlMaxSize: '32MB',
|
||||||
|
|
||||||
|
/*
|
||||||
|
Proxy URL uploads.
|
||||||
|
NOTE: Set to falsy value to disable.
|
||||||
|
|
||||||
|
Available templates:
|
||||||
|
{url} = full URL (encoded & with protocol)
|
||||||
|
{url-noprot} = URL without protocol (images.weserv.nl prefers this format)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
https://images.weserv.nl/?url={url-noprot}
|
||||||
|
will become:
|
||||||
|
https://images.weserv.nl/?url=example.com/assets/image.png
|
||||||
|
*/
|
||||||
|
urlProxy: 'https://images.weserv.nl/?url={url-noprot}',
|
||||||
|
|
||||||
|
/*
|
||||||
|
Disclaimer message that will be printed in the URL uploads form.
|
||||||
|
Supports HTML. Be safe though.
|
||||||
|
*/
|
||||||
|
urlDisclaimerMessage: 'URL uploads are being proxied and compressed by <a href="https://images.weserv.nl/" target="_blank" rel="noopener">images.weserv.nl</a>. By using this feature, you agree to their <a href="https://github.com/weserv/images/blob/4.x/Privacy-Policy.md" target="_blank" rel="noopener">Privacy Policy</a>.',
|
||||||
|
|
||||||
|
/*
|
||||||
|
Filter mode for URL uploads.
|
||||||
|
Can be 'blacklist', 'whitelist', or 'inherit'.
|
||||||
|
'inherit' => inherit primary extensions filter (extensionsFilter option).
|
||||||
|
The rest are paired with urlExtensionsFilter option below and should be self-explanatory.
|
||||||
|
When this is not set to any of the 3 values, this will fallback to 'inherit'.
|
||||||
|
*/
|
||||||
|
urlExtensionsFilterMode: 'whitelist',
|
||||||
|
|
||||||
|
/*
|
||||||
|
An array of extensions that are allowed for URL uploads.
|
||||||
|
Intented for URL proxies that only support certain extensions.
|
||||||
|
This will parse the extensions from the URLs, so URLs that do not end with
|
||||||
|
the file's extensions will always be rejected
|
||||||
|
Queries and segments in the URLs will be bypassed when parsing.
|
||||||
|
NOTE: Set to falsy value to disable filters.
|
||||||
|
*/
|
||||||
|
urlExtensionsFilter: [
|
||||||
|
'.gif',
|
||||||
|
'.jpg',
|
||||||
|
'.jpeg',
|
||||||
|
'.png',
|
||||||
|
'.bmp',
|
||||||
|
'.xbm',
|
||||||
|
'.webp'
|
||||||
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Scan files using ClamAV through clamd.
|
Scan files using ClamAV through clamd.
|
||||||
*/
|
*/
|
||||||
@ -130,7 +191,7 @@ module.exports = {
|
|||||||
by the size specified in "chunkSize". People will still be able to upload bigger files with
|
by the size specified in "chunkSize". People will still be able to upload bigger files with
|
||||||
the API as long as they don't surpass the limit specified in the "maxSize" option above.
|
the API as long as they don't surpass the limit specified in the "maxSize" option above.
|
||||||
Total size of the whole chunks will also later be checked against the "maxSize" option.
|
Total size of the whole chunks will also later be checked against the "maxSize" option.
|
||||||
NOTE: Set to falsy value (false, null, etc.) to disable chunked uploads.
|
NOTE: Set to falsy value to disable chunked uploads.
|
||||||
*/
|
*/
|
||||||
chunkSize: '10MB',
|
chunkSize: '10MB',
|
||||||
|
|
||||||
@ -184,13 +245,13 @@ module.exports = {
|
|||||||
/*
|
/*
|
||||||
This option will limit how many times it will try to
|
This option will limit how many times it will try to
|
||||||
generate a new random name when a collision occurrs.
|
generate a new random name when a collision occurrs.
|
||||||
The shorter the length is, the higher the chance for a collision to occur.
|
Generally, the shorter the length is, the higher the chance for a collision to occur.
|
||||||
This applies to both file name and album identifier.
|
This applies to both file name and album identifier.
|
||||||
*/
|
*/
|
||||||
maxTries: 1,
|
maxTries: 3,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Thumbnails are only for the dashboard.
|
Thumbnails are only used in the dashboard and album's public pages.
|
||||||
You need to install a separate binary called ffmpeg (https://ffmpeg.org/) for video thumbnails.
|
You need to install a separate binary called ffmpeg (https://ffmpeg.org/) for video thumbnails.
|
||||||
*/
|
*/
|
||||||
generateThumbs: {
|
generateThumbs: {
|
||||||
@ -214,7 +275,7 @@ module.exports = {
|
|||||||
No-JS uploader page will not chunk the uploads, so it's recommended to change this
|
No-JS uploader page will not chunk the uploads, so it's recommended to change this
|
||||||
into the maximum upload size you have in Cloudflare.
|
into the maximum upload size you have in Cloudflare.
|
||||||
This limit will only be applied to the subtitle in the page.
|
This limit will only be applied to the subtitle in the page.
|
||||||
NOTE: Set to falsy value (false, null, etc.) to inherit "maxSize" option.
|
NOTE: Set to falsy value to inherit "maxSize" option.
|
||||||
*/
|
*/
|
||||||
noJsMaxSize: '100MB',
|
noJsMaxSize: '100MB',
|
||||||
|
|
||||||
@ -223,7 +284,7 @@ module.exports = {
|
|||||||
API route (homeDomain/api/album/zip/*), with this option you can limit the
|
API route (homeDomain/api/album/zip/*), with this option you can limit the
|
||||||
maximum total size of files in an album that can be zipped.
|
maximum total size of files in an album that can be zipped.
|
||||||
Cloudflare will not cache files bigger than 512MB.
|
Cloudflare will not cache files bigger than 512MB.
|
||||||
NOTE: Set to falsy value (false, null, etc.) to disable max total size.
|
NOTE: Set to falsy value to disable max total size.
|
||||||
*/
|
*/
|
||||||
zipMaxTotalSize: '512MB',
|
zipMaxTotalSize: '512MB',
|
||||||
|
|
||||||
|
@ -72,27 +72,34 @@ const upload = multer({
|
|||||||
delete req.body[key]
|
delete req.body[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.chunkindex)
|
if (req.body.chunkindex) {
|
||||||
if (chunkedUploads && parseInt(req.body.totalfilesize) > maxSizeBytes) {
|
if (!chunkedUploads)
|
||||||
// This will not be true if "totalfilesize" key does not exist, since "NaN > number" is false.
|
|
||||||
// eslint-disable-next-line standard/no-callback-literal
|
|
||||||
return cb('Chunk error occurred. Total file size is larger than the maximum file size.')
|
|
||||||
} else if (!chunkedUploads) {
|
|
||||||
// eslint-disable-next-line standard/no-callback-literal
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
return cb('Chunked uploads are disabled at the moment.')
|
return cb('Chunked uploads are disabled at the moment.')
|
||||||
|
|
||||||
|
const totalfilesize = parseInt(req.body.totalfilesize)
|
||||||
|
if (!isNaN(totalfilesize)) {
|
||||||
|
if (config.filterEmptyFile && totalfilesize === 0)
|
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
|
return cb('Empty files are not allowed.')
|
||||||
|
if (totalfilesize > maxSizeBytes)
|
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
|
return cb('Chunk error occurred. Total file size is larger than the maximum file size.')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cb(null, true)
|
return cb(null, true)
|
||||||
}
|
}
|
||||||
}).array('files[]')
|
}).array('files[]')
|
||||||
|
|
||||||
uploadsController.isExtensionFiltered = extname => {
|
uploadsController.isExtensionFiltered = extname => {
|
||||||
|
// If empty extension needs to be filtered
|
||||||
if (!extname && config.filterNoExtension) return true
|
if (!extname && config.filterNoExtension) return true
|
||||||
// If there are extensions that have to be filtered
|
// If there are extensions that have to be filtered
|
||||||
if (extname && config.extensionsFilter && config.extensionsFilter.length) {
|
if (extname && Array.isArray(config.extensionsFilter) && config.extensionsFilter.length) {
|
||||||
const match = config.extensionsFilter.some(extension => extname === extension.toLowerCase())
|
const match = config.extensionsFilter.some(extension => extname === extension.toLowerCase())
|
||||||
if ((config.filterBlacklist && match) || (!config.filterBlacklist && !match))
|
const whitelist = config.extensionsFilterMode === 'whitelist'
|
||||||
return true
|
if ((!whitelist && match) || (whitelist && !match)) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -195,6 +202,13 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (config.filterEmptyFile && infoMap.some(file => file.data.size === 0)) {
|
||||||
|
infoMap.forEach(file => {
|
||||||
|
utils.deleteFile(file.data.filename, req.app.get('uploads-set')).catch(console.error)
|
||||||
|
})
|
||||||
|
return erred('Empty files are not allowed.')
|
||||||
|
}
|
||||||
|
|
||||||
if (config.uploads.scan && config.uploads.scan.enabled) {
|
if (config.uploads.scan && config.uploads.scan.enabled) {
|
||||||
const scan = await uploadsController.scanFiles(req, infoMap)
|
const scan = await uploadsController.scanFiles(req, infoMap)
|
||||||
if (scan) return erred(scan)
|
if (scan) return erred(scan)
|
||||||
@ -225,11 +239,29 @@ uploadsController.actuallyUploadByUrl = async (req, res, user, albumid) => {
|
|||||||
|
|
||||||
let iteration = 0
|
let iteration = 0
|
||||||
const infoMap = []
|
const infoMap = []
|
||||||
for (const url of urls) {
|
for (let url of urls) {
|
||||||
const original = path.basename(url).split(/[?#]/)[0]
|
const original = path.basename(url).split(/[?#]/)[0]
|
||||||
const extension = utils.extname(original)
|
const extname = utils.extname(original)
|
||||||
if (uploadsController.isExtensionFiltered(extension))
|
|
||||||
return erred(`${extension.substr(1).toUpperCase()} files are not permitted due to security reasons.`)
|
// Extensions filter
|
||||||
|
let filtered = false
|
||||||
|
if (['blacklist', 'whitelist'].includes(config.uploads.urlExtensionsFilterMode))
|
||||||
|
if (Array.isArray(config.uploads.urlExtensionsFilter) && config.uploads.urlExtensionsFilter.length) {
|
||||||
|
const match = config.uploads.urlExtensionsFilter.some(extension => extname === extension.toLowerCase())
|
||||||
|
const whitelist = config.uploads.urlExtensionsFilterMode === 'whitelist'
|
||||||
|
filtered = ((!whitelist && match) || (whitelist && !match))
|
||||||
|
} else {
|
||||||
|
return erred('config.uploads.urlExtensionsFilter is not an array or is an empty array, please contact site owner.')
|
||||||
|
}
|
||||||
|
else filtered = uploadsController.isExtensionFiltered(extname)
|
||||||
|
|
||||||
|
if (filtered)
|
||||||
|
return erred(`${extname ? `${extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted due to security reasons.`)
|
||||||
|
|
||||||
|
if (config.uploads.urlProxy)
|
||||||
|
url = config.uploads.urlProxy
|
||||||
|
.replace(/{url}/g, encodeURIComponent(url))
|
||||||
|
.replace(/{url-noprot}/g, encodeURIComponent(url.replace(/^https?:\/\//, '')))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fetchHead = await fetch(url, { method: 'HEAD' })
|
const fetchHead = await fetch(url, { method: 'HEAD' })
|
||||||
@ -244,7 +276,10 @@ uploadsController.actuallyUploadByUrl = async (req, res, user, albumid) => {
|
|||||||
if (size > urlMaxSizeBytes)
|
if (size > urlMaxSizeBytes)
|
||||||
return erred('File too large.')
|
return erred('File too large.')
|
||||||
|
|
||||||
// limit max response body size with content-length
|
if (config.filterEmptyFile && size === 0)
|
||||||
|
return erred('Empty files are not allowed.')
|
||||||
|
|
||||||
|
// Limit max response body size with the size reported by Content-Length
|
||||||
const fetchFile = await fetch(url, { size })
|
const fetchFile = await fetch(url, { size })
|
||||||
if (fetchFile.status !== 200)
|
if (fetchFile.status !== 200)
|
||||||
return erred(`${fetchHead.status} ${fetchHead.statusText}`)
|
return erred(`${fetchHead.status} ${fetchHead.statusText}`)
|
||||||
@ -252,7 +287,7 @@ uploadsController.actuallyUploadByUrl = async (req, res, user, albumid) => {
|
|||||||
const file = await fetchFile.buffer()
|
const file = await fetchFile.buffer()
|
||||||
|
|
||||||
const length = uploadsController.getFileNameLength(req)
|
const length = uploadsController.getFileNameLength(req)
|
||||||
const name = await uploadsController.getUniqueRandomName(length, extension, req.app.get('uploads-set'))
|
const name = await uploadsController.getUniqueRandomName(length, extname, req.app.get('uploads-set'))
|
||||||
|
|
||||||
const destination = path.join(uploadsDir, name)
|
const destination = path.join(uploadsDir, name)
|
||||||
fs.writeFile(destination, file, async error => {
|
fs.writeFile(destination, file, async error => {
|
||||||
@ -342,12 +377,12 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
|||||||
}
|
}
|
||||||
if (file.count < chunkNames.length) return erred('Chunks count mismatch.')
|
if (file.count < chunkNames.length) return erred('Chunks count mismatch.')
|
||||||
|
|
||||||
const extension = typeof file.original === 'string' ? utils.extname(file.original) : ''
|
const extname = typeof file.original === 'string' ? utils.extname(file.original) : ''
|
||||||
if (uploadsController.isExtensionFiltered(extension))
|
if (uploadsController.isExtensionFiltered(extname))
|
||||||
return erred(`${extension.substr(1).toUpperCase()} files are not permitted due to security reasons.`)
|
return erred(`${extname ? `${extname.substr(1).toUpperCase()} files` : 'Files with no extension'} are not permitted due to security reasons.`)
|
||||||
|
|
||||||
const length = uploadsController.getFileNameLength(req)
|
const length = uploadsController.getFileNameLength(req)
|
||||||
const name = await uploadsController.getUniqueRandomName(length, extension, req.app.get('uploads-set'))
|
const name = await uploadsController.getUniqueRandomName(length, extname, req.app.get('uploads-set'))
|
||||||
.catch(erred)
|
.catch(erred)
|
||||||
if (!name) return
|
if (!name) return
|
||||||
|
|
||||||
@ -360,12 +395,19 @@ uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
|
|||||||
const chunksTotalSize = await uploadsController.getTotalSize(uuidDir, chunkNames)
|
const chunksTotalSize = await uploadsController.getTotalSize(uuidDir, chunkNames)
|
||||||
.catch(erred)
|
.catch(erred)
|
||||||
if (typeof chunksTotalSize !== 'number') return
|
if (typeof chunksTotalSize !== 'number') return
|
||||||
if (chunksTotalSize > maxSizeBytes) {
|
|
||||||
|
const isEmpty = config.filterEmptyFile && (chunksTotalSize === 0)
|
||||||
|
const isBigger = chunksTotalSize > maxSizeBytes
|
||||||
|
if (isEmpty || isBigger) {
|
||||||
// Delete all chunks and remove chunks dir
|
// Delete all chunks and remove chunks dir
|
||||||
const chunksCleaned = await uploadsController.cleanUpChunks(uuidDir, chunkNames)
|
const chunksCleaned = await uploadsController.cleanUpChunks(uuidDir, chunkNames)
|
||||||
.catch(erred)
|
.catch(erred)
|
||||||
if (!chunksCleaned) return
|
if (!chunksCleaned) return
|
||||||
return erred(`Total chunks size is bigger than ${maxSize}.`)
|
|
||||||
|
if (isEmpty)
|
||||||
|
return erred('Empty files are not allowed.')
|
||||||
|
else
|
||||||
|
return erred(`Total chunks size is bigger than ${maxSize}.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append all chunks
|
// Append all chunks
|
||||||
@ -523,13 +565,7 @@ uploadsController.formatInfoMap = (req, res, user, infoMap) => {
|
|||||||
timestamp: Math.floor(Date.now() / 1000)
|
timestamp: Math.floor(Date.now() / 1000)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
utils.deleteFile(info.data.filename).catch(console.error)
|
utils.deleteFile(info.data.filename, req.app.get('uploads-set')).catch(console.error)
|
||||||
const set = req.app.get('uploads-set')
|
|
||||||
if (set) {
|
|
||||||
const identifier = info.data.filename.split('.')[0]
|
|
||||||
set.delete(identifier)
|
|
||||||
// console.log(`Removed ${identifier} from identifiers cache (formatInfoMap)`)
|
|
||||||
}
|
|
||||||
existingFiles.push(dbFile)
|
existingFiles.push(dbFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +584,7 @@ uploadsController.scanFiles = (req, infoMap) => {
|
|||||||
for (const info of infoMap)
|
for (const info of infoMap)
|
||||||
scanner.scanFile(info.path).then(reply => {
|
scanner.scanFile(info.path).then(reply => {
|
||||||
if (!reply.includes('OK') || reply.includes('FOUND')) {
|
if (!reply.includes('OK') || reply.includes('FOUND')) {
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const virus = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '')
|
const virus = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '')
|
||||||
console.log(`ClamAV: ${info.data.filename}: ${virus} FOUND.`)
|
console.log(`ClamAV: ${info.data.filename}: ${virus} FOUND.`)
|
||||||
return resolve(virus)
|
return resolve(virus)
|
||||||
@ -696,9 +732,8 @@ uploadsController.list = async (req, res) => {
|
|||||||
|
|
||||||
// Only push usernames if we are a moderator
|
// Only push usernames if we are a moderator
|
||||||
if (all && ismoderator)
|
if (all && ismoderator)
|
||||||
if (file.userid !== undefined && file.userid !== null && file.userid !== '') {
|
if (file.userid !== undefined && file.userid !== null && file.userid !== '')
|
||||||
userids.push(file.userid)
|
userids.push(file.userid)
|
||||||
}
|
|
||||||
|
|
||||||
file.extname = utils.extname(file.name)
|
file.extname = utils.extname(file.name)
|
||||||
if (utils.mayGenerateThumb(file.extname))
|
if (utils.mayGenerateThumb(file.extname))
|
||||||
|
@ -197,14 +197,19 @@ utilsController.generateThumbs = (name, force) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
utilsController.deleteFile = file => {
|
utilsController.deleteFile = (filename, set) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const extname = utilsController.extname(file)
|
const extname = utilsController.extname(filename)
|
||||||
return fs.unlink(path.join(uploadsDir, file), error => {
|
return fs.unlink(path.join(uploadsDir, filename), error => {
|
||||||
if (error && error.code !== 'ENOENT') return reject(error)
|
if (error && error.code !== 'ENOENT') return reject(error)
|
||||||
|
const identifier = filename.split('.')[0]
|
||||||
|
// eslint-disable-next-line curly
|
||||||
|
if (set) {
|
||||||
|
set.delete(identifier)
|
||||||
|
// console.log(`Removed ${identifier} from identifiers cache (deleteFile)`)
|
||||||
|
}
|
||||||
if (utilsController.imageExtensions.includes(extname) || utilsController.videoExtensions.includes(extname)) {
|
if (utilsController.imageExtensions.includes(extname) || utilsController.videoExtensions.includes(extname)) {
|
||||||
const thumb = file.substr(0, file.lastIndexOf('.')) + '.png'
|
const thumb = `${identifier}.png`
|
||||||
return fs.unlink(path.join(thumbsDir, thumb), error => {
|
return fs.unlink(path.join(thumbsDir, thumb), error => {
|
||||||
if (error && error.code !== 'ENOENT') return reject(error)
|
if (error && error.code !== 'ENOENT') return reject(error)
|
||||||
resolve(true)
|
resolve(true)
|
||||||
|
59
lolisafe.js
59
lolisafe.js
@ -31,7 +31,7 @@ fs.existsSync(`./${config.uploads.folder}/thumbs`) || fs.mkdirSync(`./${config.u
|
|||||||
fs.existsSync(`./${config.uploads.folder}/zips`) || fs.mkdirSync(`./${config.uploads.folder}/zips`)
|
fs.existsSync(`./${config.uploads.folder}/zips`) || fs.mkdirSync(`./${config.uploads.folder}/zips`)
|
||||||
|
|
||||||
safe.use(helmet())
|
safe.use(helmet())
|
||||||
safe.set('trust proxy', 1)
|
if (config.trustProxy) safe.set('trust proxy', 1)
|
||||||
|
|
||||||
// https://mozilla.github.io/nunjucks/api.html#configure
|
// https://mozilla.github.io/nunjucks/api.html#configure
|
||||||
nunjucks.configure('views', {
|
nunjucks.configure('views', {
|
||||||
@ -64,31 +64,38 @@ safe.use('/', album)
|
|||||||
safe.use('/', nojs)
|
safe.use('/', nojs)
|
||||||
safe.use('/api', api)
|
safe.use('/api', api)
|
||||||
|
|
||||||
for (const page of config.pages)
|
if (Array.isArray(config.pages) && config.pages.length) {
|
||||||
if (fs.existsSync(`./pages/custom/${page}.html`)) {
|
for (const page of config.pages)
|
||||||
safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, {
|
if (fs.existsSync(`./pages/custom/${page}.html`)) {
|
||||||
root: './pages/custom/'
|
safe.get(`/${page}`, (req, res, next) => res.sendFile(`${page}.html`, {
|
||||||
}))
|
root: './pages/custom/'
|
||||||
} else if (page === 'home') {
|
}))
|
||||||
safe.get('/', (req, res, next) => res.render('home', {
|
} else if (page === 'home') {
|
||||||
maxSize: config.uploads.maxSize,
|
safe.get('/', (req, res, next) => res.render('home', {
|
||||||
urlMaxSize: config.uploads.urlMaxSize,
|
maxSize: config.uploads.maxSize,
|
||||||
gitHash: safe.get('git-hash'),
|
urlMaxSize: config.uploads.urlMaxSize,
|
||||||
urlDuckDuckGoProxy: config.uploads.urlDuckDuckGoProxy
|
urlDisclaimerMessage: config.uploads.urlDisclaimerMessage,
|
||||||
}))
|
urlExtensionsFilterMode: config.uploads.urlExtensionsFilterMode,
|
||||||
} else if (page === 'faq') {
|
urlExtensionsFilter: config.uploads.urlExtensionsFilter,
|
||||||
const fileLength = config.uploads.fileLength
|
gitHash: safe.get('git-hash')
|
||||||
safe.get('/faq', (req, res, next) => res.render('faq', {
|
}))
|
||||||
filterBlacklist: config.filterBlacklist,
|
} else if (page === 'faq') {
|
||||||
extensionsFilter: config.extensionsFilter,
|
const fileLength = config.uploads.fileLength
|
||||||
fileLength,
|
safe.get('/faq', (req, res, next) => res.render('faq', {
|
||||||
tooShort: (fileLength.max - fileLength.default) > (fileLength.default - fileLength.min),
|
whitelist: config.extensionsFilterMode === 'whitelist',
|
||||||
noJsMaxSize: parseInt(config.cloudflare.noJsMaxSize) < parseInt(config.uploads.maxSize),
|
extensionsFilter: config.extensionsFilter,
|
||||||
chunkSize: config.uploads.chunkSize
|
fileLength,
|
||||||
}))
|
tooShort: (fileLength.max - fileLength.default) > (fileLength.default - fileLength.min),
|
||||||
} else {
|
noJsMaxSize: parseInt(config.cloudflare.noJsMaxSize) < parseInt(config.uploads.maxSize),
|
||||||
safe.get(`/${page}`, (req, res, next) => res.render(page))
|
chunkSize: config.uploads.chunkSize
|
||||||
}
|
}))
|
||||||
|
} else {
|
||||||
|
safe.get(`/${page}`, (req, res, next) => res.render(page))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('config.pages is not an array or is an empty array. This won\t do!')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
safe.use((req, res, next) => {
|
safe.use((req, res, next) => {
|
||||||
res.status(404).sendFile(config.errorPages[404], { root: config.errorPages.rootDir })
|
res.status(404).sendFile(config.errorPages[404], { root: config.errorPages.rootDir })
|
||||||
|
@ -87,9 +87,9 @@
|
|||||||
<h2 class='subtitle'>What are the allowed extensions here?</h2>
|
<h2 class='subtitle'>What are the allowed extensions here?</h2>
|
||||||
<article class="message">
|
<article class="message">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
{% if extensionsFilter.length and filterBlacklist -%}
|
{% if extensionsFilter.length and not whitelist -%}
|
||||||
We support any file extensions except the following: {{ extensionsFilter | join(', ') }}.
|
We support any file extensions except the following: {{ extensionsFilter | join(', ') }}.
|
||||||
{%- elif extensionsFilter.length and not filterBlacklist -%}
|
{%- elif extensionsFilter.length and whitelist -%}
|
||||||
We only support the following extensions: {{ extensionsFilter | join(', ') }}.
|
We only support the following extensions: {{ extensionsFilter | join(', ') }}.
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
We support any file extensions.
|
We support any file extensions.
|
||||||
|
@ -78,10 +78,16 @@
|
|||||||
{% if urlMaxSize !== maxSize -%}
|
{% if urlMaxSize !== maxSize -%}
|
||||||
Maximum file size for URL upload is <span style="font-weight: bold">{{ urlMaxSize }}</span>.
|
Maximum file size for URL upload is <span style="font-weight: bold">{{ urlMaxSize }}</span>.
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- if urlMaxSize and urlMaxSize !== maxSize and urlDuckDuckGoProxy %}<br>{% endif -%}
|
|
||||||
{%- if urlDuckDuckGoProxy %}
|
{% if urlExtensionsFilter.length and (urlExtensionsFilterMode === 'blacklist') -%}
|
||||||
Since we're using DuckDuckGo's proxy, the URLs have to be direct links.
|
Blacklisted extensions: {{ urlExtensionsFilter | join(', ') }}.
|
||||||
{% endif -%}
|
{%- elif urlExtensionsFilter.length and (urlExtensionsFilterMode === 'whitelist') -%}
|
||||||
|
Whitelisted extensions: {{ urlExtensionsFilter | join(', ') }}.
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if urlDisclaimerMessage %}
|
||||||
|
{{ urlDisclaimerMessage | safe }}
|
||||||
|
{% endif -%}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
Loading…
Reference in New Issue
Block a user