You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.0 KiB
129 lines
4.0 KiB
|
2 years ago
|
/* eslint-disable max-classes-per-file, class-methods-use-this */
|
||
|
|
/* global AggregateError */
|
||
|
|
import prettierBytes from '@transloadit/prettier-bytes'
|
||
|
|
import match from 'mime-match'
|
||
|
|
|
||
|
|
const defaultOptions = {
|
||
|
|
maxFileSize: null,
|
||
|
|
minFileSize: null,
|
||
|
|
maxTotalFileSize: null,
|
||
|
|
maxNumberOfFiles: null,
|
||
|
|
minNumberOfFiles: null,
|
||
|
|
allowedFileTypes: null,
|
||
|
|
requiredMetaFields: [],
|
||
|
|
}
|
||
|
|
|
||
|
|
class RestrictionError extends Error {
|
||
|
|
isRestriction = true
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof AggregateError === 'undefined') {
|
||
|
|
// eslint-disable-next-line no-global-assign
|
||
|
|
// TODO: remove this "polyfill" in the next major.
|
||
|
|
globalThis.AggregateError = class AggregateError extends Error {
|
||
|
|
constructor (errors, message) {
|
||
|
|
super(message)
|
||
|
|
this.errors = errors
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class Restricter {
|
||
|
|
constructor (getOpts, i18n) {
|
||
|
|
this.i18n = i18n
|
||
|
|
this.getOpts = () => {
|
||
|
|
const opts = getOpts()
|
||
|
|
|
||
|
|
if (opts.restrictions.allowedFileTypes != null
|
||
|
|
&& !Array.isArray(opts.restrictions.allowedFileTypes)) {
|
||
|
|
throw new TypeError('`restrictions.allowedFileTypes` must be an array')
|
||
|
|
}
|
||
|
|
return opts
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
validate (file, files) {
|
||
|
|
const { maxFileSize, minFileSize, maxTotalFileSize, maxNumberOfFiles, allowedFileTypes } = this.getOpts().restrictions
|
||
|
|
|
||
|
|
if (maxNumberOfFiles) {
|
||
|
|
const nonGhostFiles = files.filter(f => !f.isGhost)
|
||
|
|
if (nonGhostFiles.length + 1 > maxNumberOfFiles) {
|
||
|
|
throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (allowedFileTypes) {
|
||
|
|
const isCorrectFileType = allowedFileTypes.some((type) => {
|
||
|
|
// check if this is a mime-type
|
||
|
|
if (type.includes('/')) {
|
||
|
|
if (!file.type) return false
|
||
|
|
return match(file.type.replace(/;.*?$/, ''), type)
|
||
|
|
}
|
||
|
|
|
||
|
|
// otherwise this is likely an extension
|
||
|
|
if (type[0] === '.' && file.extension) {
|
||
|
|
return file.extension.toLowerCase() === type.slice(1).toLowerCase()
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
})
|
||
|
|
|
||
|
|
if (!isCorrectFileType) {
|
||
|
|
const allowedFileTypesString = allowedFileTypes.join(', ')
|
||
|
|
throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString }))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We can't check maxTotalFileSize if the size is unknown.
|
||
|
|
if (maxTotalFileSize && file.size != null) {
|
||
|
|
const totalFilesSize = files.reduce((total, f) => (total + f.size), file.size)
|
||
|
|
|
||
|
|
if (totalFilesSize > maxTotalFileSize) {
|
||
|
|
throw new RestrictionError(this.i18n('exceedsSize', {
|
||
|
|
size: prettierBytes(maxTotalFileSize),
|
||
|
|
file: file.name,
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We can't check maxFileSize if the size is unknown.
|
||
|
|
if (maxFileSize && file.size != null && file.size > maxFileSize) {
|
||
|
|
throw new RestrictionError(this.i18n('exceedsSize', {
|
||
|
|
size: prettierBytes(maxFileSize),
|
||
|
|
file: file.name,
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
// We can't check minFileSize if the size is unknown.
|
||
|
|
if (minFileSize && file.size != null && file.size < minFileSize) {
|
||
|
|
throw new RestrictionError(this.i18n('inferiorSize', {
|
||
|
|
size: prettierBytes(minFileSize),
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
validateMinNumberOfFiles (files) {
|
||
|
|
const { minNumberOfFiles } = this.getOpts().restrictions
|
||
|
|
if (Object.keys(files).length < minNumberOfFiles) {
|
||
|
|
throw new RestrictionError(this.i18n('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles }))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
getMissingRequiredMetaFields (file) {
|
||
|
|
const error = new RestrictionError(this.i18n('missingRequiredMetaFieldOnFile', { fileName: file.name }))
|
||
|
|
const { requiredMetaFields } = this.getOpts().restrictions
|
||
|
|
// TODO: migrate to Object.hasOwn in the next major.
|
||
|
|
const own = Object.prototype.hasOwnProperty
|
||
|
|
const missingFields = []
|
||
|
|
|
||
|
|
for (const field of requiredMetaFields) {
|
||
|
|
if (!own.call(file.meta, field) || file.meta[field] === '') {
|
||
|
|
missingFields.push(field)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { missingFields, error }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export { Restricter, defaultOptions, RestrictionError }
|