{"version":3,"sources":["../../src/uploads/checkFileRestrictions.ts"],"sourcesContent":["import { fileTypeFromBuffer, fileTypeFromFile } from 'file-type'\nimport fs from 'fs/promises'\n\nimport type { checkFileRestrictionsParams, FileAllowList } from './types.js'\n\nimport { ValidationError } from '../errors/index.js'\nimport { validateMimeType } from '../utilities/validateMimeType.js'\nimport { validatePDF } from '../utilities/validatePDF.js'\nimport { detectSvgFromXml } from './detectSvgFromXml.js'\nimport { getFileTypeFallback } from './getFileTypeFallback.js'\nimport { validateSvg } from './validateSvg.js'\n\n/**\n * Restricted file types and their extensions.\n */\nexport const RESTRICTED_FILE_EXT_AND_TYPES: FileAllowList = [\n  { extensions: ['exe', 'dll'], mimeType: 'application/x-msdownload' },\n  { extensions: ['exe', 'com', 'app', 'action'], mimeType: 'application/x-executable' },\n  { extensions: ['bat', 'cmd'], mimeType: 'application/x-msdos-program' },\n  { extensions: ['exe', 'com'], mimeType: 'application/x-ms-dos-executable' },\n  { extensions: ['dmg'], mimeType: 'application/x-apple-diskimage' },\n  { extensions: ['deb'], mimeType: 'application/x-debian-package' },\n  { extensions: ['rpm'], mimeType: 'application/x-redhat-package-manager' },\n  { extensions: ['exe', 'dll'], mimeType: 'application/vnd.microsoft.portable-executable' },\n  { extensions: ['msi'], mimeType: 'application/x-msi' },\n  { extensions: ['jar', 'ear', 'war'], mimeType: 'application/java-archive' },\n  { extensions: ['desktop'], mimeType: 'application/x-desktop' },\n  { extensions: ['cpl'], mimeType: 'application/x-cpl' },\n  { extensions: ['lnk'], mimeType: 'application/x-ms-shortcut' },\n  { extensions: ['pkg'], mimeType: 'application/x-apple-installer' },\n  { extensions: ['htm', 'html', 'shtml', 'xhtml'], mimeType: 'text/html' },\n  { extensions: ['php', 'phtml'], mimeType: 'application/x-httpd-php' },\n  { extensions: ['js', 'jse'], mimeType: 'text/javascript' },\n  { extensions: ['jsp'], mimeType: 'application/x-jsp' },\n  { extensions: ['py'], mimeType: 'text/x-python' },\n  { extensions: ['rb'], mimeType: 'text/x-ruby' },\n  { extensions: ['pl'], mimeType: 'text/x-perl' },\n  { extensions: ['ps1', 'psc1', 'psd1', 'psh', 'psm1'], mimeType: 'application/x-powershell' },\n  { extensions: ['vbe', 'vbs'], mimeType: 'application/x-vbscript' },\n  { extensions: ['ws', 'wsc', 'wsf', 'wsh'], mimeType: 'application/x-ms-wsh' },\n  { extensions: ['scr'], mimeType: 'application/x-msdownload' },\n  { extensions: ['asp', 'aspx'], mimeType: 'application/x-asp' },\n  { extensions: ['hta'], mimeType: 'application/x-hta' },\n  { extensions: ['reg'], mimeType: 'application/x-registry' },\n  { extensions: ['url'], mimeType: 'application/x-url' },\n  { extensions: ['workflow'], mimeType: 'application/x-workflow' },\n  { extensions: ['command'], mimeType: 'application/x-command' },\n]\n\nexport const checkFileRestrictions = async ({\n  collection,\n  file,\n  req,\n}: checkFileRestrictionsParams): Promise<void> => {\n  const errors: string[] = []\n  const { upload: uploadConfig } = collection\n  const configMimeTypes =\n    uploadConfig &&\n    typeof uploadConfig === 'object' &&\n    'mimeTypes' in uploadConfig &&\n    Array.isArray(uploadConfig.mimeTypes)\n      ? uploadConfig.mimeTypes\n      : []\n\n  const allowRestrictedFileTypes =\n    uploadConfig && typeof uploadConfig === 'object' && 'allowRestrictedFileTypes' in uploadConfig\n      ? (uploadConfig as { allowRestrictedFileTypes?: boolean }).allowRestrictedFileTypes\n      : false\n\n  const expectsDetectableType = (mimeType: string): boolean => {\n    const textBasedTypes = ['/svg', 'image/svg+xml', 'image/x-xbitmap', 'image/x-xpixmap']\n\n    if (textBasedTypes.includes(mimeType)) {\n      return false\n    }\n\n    return (\n      mimeType.startsWith('image/') ||\n      mimeType.startsWith('video/') ||\n      mimeType.startsWith('audio/') ||\n      mimeType === 'application/pdf'\n    )\n  }\n\n  // Skip validation if `allowRestrictedFileTypes` is true\n  if (allowRestrictedFileTypes) {\n    return\n  }\n\n  // For temp files, use fileTypeFromFile so large files (e.g. video) are never loaded into memory\n  // just for detection. For content validation (SVG safety, PDF integrity), the full buffer is\n  // loaded lazily and only when the file type actually requires it.\n  const { tempFilePath } = file\n  const isTempFile = !!tempFilePath && (!file.data || file.data.length === 0)\n\n  // Lazily reads the full file — only reached for small text-based types (SVG, PDF).\n  let _fileBuffer: Buffer | undefined\n  const getFileBuffer = async (): Promise<Buffer> => {\n    if (_fileBuffer) {\n      return _fileBuffer\n    }\n    if (!isTempFile || !tempFilePath) {\n      return (_fileBuffer = file.data)\n    }\n    try {\n      _fileBuffer = await fs.readFile(tempFilePath)\n      return _fileBuffer\n    } catch {\n      throw new ValidationError({\n        errors: [{ message: 'Could not read uploaded file for validation.', path: 'file' }],\n      })\n    }\n  }\n\n  // Secondary mimetype check to assess file type from buffer\n  if (configMimeTypes.length > 0) {\n    let detected\n    try {\n      detected =\n        isTempFile && tempFilePath\n          ? await fileTypeFromFile(tempFilePath)\n          : await fileTypeFromBuffer(file.data)\n    } catch {\n      throw new ValidationError({\n        errors: [{ message: 'Could not read uploaded file for type detection.', path: 'file' }],\n      })\n    }\n    const typeFromExtension = file.name.split('.').pop() || ''\n\n    // Handle SVG files that are detected as XML due to <?xml declarations\n    if (\n      detected?.mime === 'application/xml' &&\n      configMimeTypes.some(\n        (type) => type.includes('image/') && (type.includes('svg') || type === 'image/*'),\n      )\n    ) {\n      const isSvg = detectSvgFromXml(await getFileBuffer())\n      if (isSvg) {\n        detected = { ext: 'svg', mime: 'image/svg+xml' }\n      }\n    }\n\n    if (!detected) {\n      const mimeTypeFromExtension = getFileTypeFallback(file.name).mime\n      const extIsValid = validateMimeType(mimeTypeFromExtension, configMimeTypes)\n\n      if (!extIsValid) {\n        errors.push(\n          `File type ${mimeTypeFromExtension} (from extension ${typeFromExtension}) is not allowed.`,\n        )\n      } else {\n        // SVG security check (text-based files not detectable by buffer)\n        if (typeFromExtension.toLowerCase() === 'svg') {\n          const isSafeSvg = validateSvg(await getFileBuffer())\n          if (!isSafeSvg) {\n            errors.push('SVG file contains potentially harmful content.')\n          }\n        }\n\n        // PDF validation\n        if (mimeTypeFromExtension === 'application/pdf') {\n          const isValidPDF = validatePDF(await getFileBuffer())\n          if (!isValidPDF) {\n            errors.push('Invalid or corrupted PDF file.')\n          }\n        }\n      }\n\n      if (expectsDetectableType(mimeTypeFromExtension)) {\n        req.payload.logger.warn(\n          `File buffer returned no detectable MIME type for ${file.name}. Falling back to extension-based validation.`,\n        )\n      }\n    }\n\n    const passesMimeTypeCheck = detected?.mime && validateMimeType(detected.mime, configMimeTypes)\n\n    if (passesMimeTypeCheck && detected?.mime === 'application/pdf') {\n      const isValidPDF = validatePDF(await getFileBuffer())\n      if (!isValidPDF) {\n        errors.push('Invalid PDF file.')\n      }\n    }\n\n    if (detected && !passesMimeTypeCheck) {\n      errors.push(`Invalid MIME type: ${detected.mime}.`)\n    }\n  } else {\n    const isRestricted = RESTRICTED_FILE_EXT_AND_TYPES.some((type) => {\n      const hasRestrictedExt = type.extensions.some((ext) => file.name.toLowerCase().endsWith(ext))\n      const hasRestrictedMime = type.mimeType === file.mimetype\n      return hasRestrictedExt || hasRestrictedMime\n    })\n    if (isRestricted) {\n      errors.push(\n        `File type '${file.mimetype}' not allowed ${file.name}: Restricted file type detected -- set 'allowRestrictedFileTypes' to true to skip this check for this Collection.`,\n      )\n    }\n  }\n\n  if (errors.length > 0) {\n    req.payload.logger.error(errors.join(', '))\n    throw new ValidationError({\n      errors: [{ message: errors.join(', '), path: 'file' }],\n    })\n  }\n}\n"],"names":["fileTypeFromBuffer","fileTypeFromFile","fs","ValidationError","validateMimeType","validatePDF","detectSvgFromXml","getFileTypeFallback","validateSvg","RESTRICTED_FILE_EXT_AND_TYPES","extensions","mimeType","checkFileRestrictions","collection","file","req","errors","upload","uploadConfig","configMimeTypes","Array","isArray","mimeTypes","allowRestrictedFileTypes","expectsDetectableType","textBasedTypes","includes","startsWith","tempFilePath","isTempFile","data","length","_fileBuffer","getFileBuffer","readFile","message","path","detected","typeFromExtension","name","split","pop","mime","some","type","isSvg","ext","mimeTypeFromExtension","extIsValid","push","toLowerCase","isSafeSvg","isValidPDF","payload","logger","warn","passesMimeTypeCheck","isRestricted","hasRestrictedExt","endsWith","hasRestrictedMime","mimetype","error","join"],"mappings":"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"}