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.
171 lines
4.4 KiB
171 lines
4.4 KiB
|
2 years ago
|
"use strict";
|
||
|
|
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
exports.defaultOptions = exports.RestrictionError = exports.Restricter = void 0;
|
||
|
|
|
||
|
|
/* eslint-disable max-classes-per-file, class-methods-use-this */
|
||
|
|
|
||
|
|
/* global AggregateError */
|
||
|
|
const prettierBytes = require("@transloadit/prettier-bytes");
|
||
|
|
|
||
|
|
const match = require("mime-match");
|
||
|
|
|
||
|
|
const defaultOptions = {
|
||
|
|
maxFileSize: null,
|
||
|
|
minFileSize: null,
|
||
|
|
maxTotalFileSize: null,
|
||
|
|
maxNumberOfFiles: null,
|
||
|
|
minNumberOfFiles: null,
|
||
|
|
allowedFileTypes: null,
|
||
|
|
requiredMetaFields: []
|
||
|
|
};
|
||
|
|
exports.defaultOptions = defaultOptions;
|
||
|
|
|
||
|
|
class RestrictionError extends Error {
|
||
|
|
constructor() {
|
||
|
|
super(...arguments);
|
||
|
|
this.isRestriction = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
exports.RestrictionError = RestrictionError;
|
||
|
|
|
||
|
|
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
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
exports.Restricter = Restricter;
|