Restricter.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.defaultOptions = exports.RestrictionError = exports.Restricter = void 0;
  6. /* eslint-disable max-classes-per-file, class-methods-use-this */
  7. /* global AggregateError */
  8. const prettierBytes = require("@transloadit/prettier-bytes");
  9. const match = require("mime-match");
  10. const defaultOptions = {
  11. maxFileSize: null,
  12. minFileSize: null,
  13. maxTotalFileSize: null,
  14. maxNumberOfFiles: null,
  15. minNumberOfFiles: null,
  16. allowedFileTypes: null,
  17. requiredMetaFields: []
  18. };
  19. exports.defaultOptions = defaultOptions;
  20. class RestrictionError extends Error {
  21. constructor() {
  22. super(...arguments);
  23. this.isRestriction = true;
  24. }
  25. }
  26. exports.RestrictionError = RestrictionError;
  27. if (typeof AggregateError === 'undefined') {
  28. // eslint-disable-next-line no-global-assign
  29. // TODO: remove this "polyfill" in the next major.
  30. globalThis.AggregateError = class AggregateError extends Error {
  31. constructor(errors, message) {
  32. super(message);
  33. this.errors = errors;
  34. }
  35. };
  36. }
  37. class Restricter {
  38. constructor(getOpts, i18n) {
  39. this.i18n = i18n;
  40. this.getOpts = () => {
  41. const opts = getOpts();
  42. if (opts.restrictions.allowedFileTypes != null && !Array.isArray(opts.restrictions.allowedFileTypes)) {
  43. throw new TypeError('`restrictions.allowedFileTypes` must be an array');
  44. }
  45. return opts;
  46. };
  47. }
  48. validate(file, files) {
  49. const {
  50. maxFileSize,
  51. minFileSize,
  52. maxTotalFileSize,
  53. maxNumberOfFiles,
  54. allowedFileTypes
  55. } = this.getOpts().restrictions;
  56. if (maxNumberOfFiles) {
  57. const nonGhostFiles = files.filter(f => !f.isGhost);
  58. if (nonGhostFiles.length + 1 > maxNumberOfFiles) {
  59. throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', {
  60. smart_count: maxNumberOfFiles
  61. })}`);
  62. }
  63. }
  64. if (allowedFileTypes) {
  65. const isCorrectFileType = allowedFileTypes.some(type => {
  66. // check if this is a mime-type
  67. if (type.includes('/')) {
  68. if (!file.type) return false;
  69. return match(file.type.replace(/;.*?$/, ''), type);
  70. } // otherwise this is likely an extension
  71. if (type[0] === '.' && file.extension) {
  72. return file.extension.toLowerCase() === type.slice(1).toLowerCase();
  73. }
  74. return false;
  75. });
  76. if (!isCorrectFileType) {
  77. const allowedFileTypesString = allowedFileTypes.join(', ');
  78. throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', {
  79. types: allowedFileTypesString
  80. }));
  81. }
  82. } // We can't check maxTotalFileSize if the size is unknown.
  83. if (maxTotalFileSize && file.size != null) {
  84. const totalFilesSize = files.reduce((total, f) => total + f.size, file.size);
  85. if (totalFilesSize > maxTotalFileSize) {
  86. throw new RestrictionError(this.i18n('exceedsSize', {
  87. size: prettierBytes(maxTotalFileSize),
  88. file: file.name
  89. }));
  90. }
  91. } // We can't check maxFileSize if the size is unknown.
  92. if (maxFileSize && file.size != null && file.size > maxFileSize) {
  93. throw new RestrictionError(this.i18n('exceedsSize', {
  94. size: prettierBytes(maxFileSize),
  95. file: file.name
  96. }));
  97. } // We can't check minFileSize if the size is unknown.
  98. if (minFileSize && file.size != null && file.size < minFileSize) {
  99. throw new RestrictionError(this.i18n('inferiorSize', {
  100. size: prettierBytes(minFileSize)
  101. }));
  102. }
  103. }
  104. validateMinNumberOfFiles(files) {
  105. const {
  106. minNumberOfFiles
  107. } = this.getOpts().restrictions;
  108. if (Object.keys(files).length < minNumberOfFiles) {
  109. throw new RestrictionError(this.i18n('youHaveToAtLeastSelectX', {
  110. smart_count: minNumberOfFiles
  111. }));
  112. }
  113. }
  114. getMissingRequiredMetaFields(file) {
  115. const error = new RestrictionError(this.i18n('missingRequiredMetaFieldOnFile', {
  116. fileName: file.name
  117. }));
  118. const {
  119. requiredMetaFields
  120. } = this.getOpts().restrictions; // TODO: migrate to Object.hasOwn in the next major.
  121. const own = Object.prototype.hasOwnProperty;
  122. const missingFields = [];
  123. for (const field of requiredMetaFields) {
  124. if (!own.call(file.meta, field) || file.meta[field] === '') {
  125. missingFields.push(field);
  126. }
  127. }
  128. return {
  129. missingFields,
  130. error
  131. };
  132. }
  133. }
  134. exports.Restricter = Restricter;