Просмотр исходного кода

Make content-disposition settings definitions more scalable

arvid-e 8 месяцев назад
Родитель
Сommit
ab58d442ca

+ 0 - 42
apps/app/src/server/routes/apiv3/configurable-mime-types.ts

@@ -1,42 +0,0 @@
-export const CONFIGURABLE_MIME_TYPES_FOR_DISPOSITION = [
-  // Common Image Types
-  'image/jpeg',
-  'image/png',
-  'image/gif',
-  'image/webp',
-  'image/bmp',
-  'image/tiff',
-  'image/x-icon',
-
-  // Document & Media Types
-  'application/pdf',
-  'text/plain',
-  'video/mp4',
-  'video/webm',
-  'video/ogg',
-  'audio/mpeg',
-  'audio/ogg',
-  'audio/wav',
-
-  // Potentially Dangerous / Executable / Scriptable Types (defaulting to attachment for security)
-  'text/html',
-  'text/javascript',
-  'application/javascript',
-  'image/svg+xml',
-  'application/xml',
-  'application/json',
-  'application/x-sh', // Shell scripts
-  'application/x-msdownload', // Executables
-  'application/octet-stream', // Generic binary
-
-  // Other Common Document Formats (often better as attachment)
-  'application/msword',
-  'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
-  'application/vnd.ms-excel',
-  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
-  'application/vnd.ms-powerpoint',
-  'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
-  'application/zip',
-  'application/x-rar-compressed',
-  'text/csv',
-];

+ 41 - 46
apps/app/src/server/routes/apiv3/content-disposition-settings.ts

@@ -7,8 +7,6 @@ import { apiV3FormValidator } from '~/server/middlewares/apiv3-form-validator';
 import { configManager } from '~/server/service/config-manager';
 import loggerFactory from '~/utils/logger';
 
-import { CONFIGURABLE_MIME_TYPES_FOR_DISPOSITION } from './configurable-mime-types';
-
 const logger = loggerFactory('growi:routes:apiv3:content-disposition-settings');
 const express = require('express');
 
@@ -22,13 +20,15 @@ const validator = {
       .withMessage('MIME type is required')
       .bail()
       .matches(/^.+\/.+$/)
-      .custom(value => CONFIGURABLE_MIME_TYPES_FOR_DISPOSITION.includes(value))
+      .custom((value) => {
+        const mimeTypeDefaults = configManager.getConfig('attachments:contentDisposition:mimeTypeDefaults');
+        return Object.keys(mimeTypeDefaults).includes(value);
+      })
       .withMessage('Invalid or unconfigurable MIME type specified.'),
 
-    body('isInline')
-      .isBoolean()
-      .withMessage('`isInline` must be a boolean.')
-      .toBoolean(),
+    body('disposition')
+      .isIn(['inline', 'attachment']) // Validate that it's one of these two strings
+      .withMessage('`disposition` must be either "inline" or "attachment".'),
   ],
 };
 
@@ -65,35 +65,23 @@ module.exports = (crowi) => {
  *               properties:
  *                 contentDispositionSettings:
  *                   type: object
- *                   description: An object mapping configurable MIME types to their current inline disposition status.
  *                   additionalProperties:
- *                     type: boolean
- *                     description: true if inline, false if attachment.
+ *                     type: string
+ *                     description: inline or attachment
  *
  */
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
-    const results = CONFIGURABLE_MIME_TYPES_FOR_DISPOSITION.map(
-      (mimeType) => {
-        const configKey = `attachments:contentDisposition:${mimeType}:inline`;
-        try {
-          const value = crowi.configManager.getConfig(configKey);
-          return { mimeType, value };
-        }
-        catch (err) {
-          logger.warn(
-            `Could not retrieve config for ${configKey}: ${err.message}`,
-          );
-          return { mimeType, value: false };
-        }
-      },
-    );
-
-    const contentDispositionSettings = {};
-    for (const result of results) {
-      contentDispositionSettings[result.mimeType] = result.value;
-    }
+    try {
+
+      const mimeTypeDefaults = crowi.configManager.getConfig('attachments:contentDisposition:mimeTypeDefaults');
+      const contentDispositionSettings: Record<string, 'inline' | 'attachment'> = mimeTypeDefaults;
 
-    return res.apiv3({ contentDispositionSettings });
+      return res.apiv3({ contentDispositionSettings });
+    }
+    catch (err) {
+      logger.error('Error retrieving content disposition settings:', err);
+      return res.apiv3Err(new ErrorV3('Failed to retrieve content disposition settings', 'get-content-disposition-failed'));
+    }
   });
 
   /**
@@ -115,11 +103,10 @@ module.exports = (crowi) => {
  *              schema:
  *                type: object
  *                required:
- *                  - isInline
+ *                  - disposition
  *                properties:
- *                  isInline:
- *                     type: boolean
- *                     description: 'Set to `true` for inline disposition, `false` for attachment disposition (e g , prompts download) '
+ *                  disposition:
+ *                     type: string
  *        responses:
  *          200:
  *            description: Successfully updated content disposition setting
@@ -133,8 +120,8 @@ module.exports = (crowi) => {
  *                       properties:
  *                         mimeType:
  *                           type: string
- *                         isInline:
- *                           type: boolean
+ *                         disposition:
+ *                           type: string
  */
   router.put(
     '/:mimeType(*)',
@@ -145,25 +132,33 @@ module.exports = (crowi) => {
     apiV3FormValidator,
     async(req, res) => {
       const { mimeType } = req.params;
-      const { isInline } = req.body;
-
-      const configKey = `attachments:contentDisposition:${mimeType}:inline`;
+      const { disposition } = req.body;
 
       try {
-        // Update the configuration in the database
-        await configManager.updateConfigs({ [configKey]: isInline });
+        const currentMimeTypeDefaults = crowi.configManager.getConfig('attachments:contentDisposition:mimeTypeDefaults') as Record<string, 'inline'
+          | 'attachment'>;
+
+        // No need for conversion, directly use the received 'disposition' string
+        const newDisposition: 'inline' | 'attachment' = disposition;
+
+        const updatedMimeTypeDefaults = {
+          ...currentMimeTypeDefaults,
+          [mimeType]: newDisposition,
+        };
+
+        await crowi.configManager.updateConfigs({ 'attachments:contentDisposition:mimeTypeDefaults': updatedMimeTypeDefaults });
 
-        // Retrieve the updated value to send back in the response
-        const updatedIsInline = await crowi.configManager.getConfig(configKey);
+        // Retrieve the updated value (optional, can just use newDisposition)
+        const updatedDispositionFromDb = crowi.configManager.getConfig('attachments:contentDisposition:mimeTypeDefaults')[mimeType];
 
         const parameters = {
           action: SupportedAction.ACTION_ADMIN_ATTACHMENT_DISPOSITION_UPDATE,
           mimeType,
-          isInline: updatedIsInline,
+          disposition: updatedDispositionFromDb,
         };
         activityEvent.emit('update', res.locals.activity._id, parameters);
 
-        return res.apiv3({ mimeType, isInline: updatedIsInline });
+        return res.apiv3({ mimeType, disposition: updatedDispositionFromDb });
       }
       catch (err) {
         const msg = `Error occurred in updating content disposition for MIME type: ${mimeType}`;

+ 46 - 169
apps/app/src/server/service/config-manager/config-definition.ts

@@ -78,44 +78,8 @@ export const CONFIG_KEYS = [
   'app:openaiThreadDeletionCronMaxMinutesUntilRequest',
   'app:openaiVectorStoreFileDeletionCronMaxMinutesUntilRequest',
 
-  // Attachment Content-Disposition settings
-  // Image Types ---
-  'attachments:contentDisposition:image/jpeg:inline',
-  'attachments:contentDisposition:image/png:inline',
-  'attachments:contentDisposition:image/gif:inline',
-  'attachments:contentDisposition:image/webp:inline',
-  'attachments:contentDisposition:image/bmp:inline',
-  'attachments:contentDisposition:image/tiff:inline',
-  'attachments:contentDisposition:image/x-icon:inline',
-  // Document & Media Types ---
-  'attachments:contentDisposition:application/pdf:inline',
-  'attachments:contentDisposition:text/plain:inline',
-  'attachments:contentDisposition:video/mp4:inline',
-  'attachments:contentDisposition:video/webm:inline',
-  'attachments:contentDisposition:video/ogg:inline',
-  'attachments:contentDisposition:audio/mpeg:inline',
-  'attachments:contentDisposition:audio/ogg:inline',
-  'attachments:contentDisposition:audio/wav:inline',
-  // Potentially Dangerous / Executable / Scriptable Types ---
-  'attachments:contentDisposition:text/html:inline',
-  'attachments:contentDisposition:text/javascript:inline',
-  'attachments:contentDisposition:application/javascript:inline',
-  'attachments:contentDisposition:image/svg+xml:inline',
-  'attachments:contentDisposition:application/xml:inline',
-  'attachments:contentDisposition:application/json:inline',
-  'attachments:contentDisposition:application/x-sh:inline',
-  'attachments:contentDisposition:application/x-msdownload:inline',
-  'attachments:contentDisposition:application/octet-stream:inline',
-  // Other Common Document Formats ---
-  'attachments:contentDisposition:application/msword:inline',
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.wordprocessingml.document:inline',
-  'attachments:contentDisposition:application/vnd.ms-excel:inline',
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:inline',
-  'attachments:contentDisposition:application/vnd.ms-powerpoint:inline',
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.presentationml.presentation:inline',
-  'attachments:contentDisposition:application/zip:inline',
-  'attachments:contentDisposition:application/x-rar-compressed:inline',
-  'attachments:contentDisposition:text/csv:inline',
+  // Content-Disposition settings for MIME types
+  'attachments:contentDisposition:mimeTypeDefaults',
 
   // Security Settings
   'security:wikiMode',
@@ -578,139 +542,52 @@ export const CONFIG_DEFINITIONS = {
   }),
 
   // Attachment Content-Disposition settings
-  'attachments:contentDisposition:image/jpeg:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_JPEG_INLINE',
-  }),
-  'attachments:contentDisposition:image/png:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_PNG_INLINE',
-  }),
-  'attachments:contentDisposition:image/gif:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_GIF_INLINE',
-  }),
-  'attachments:contentDisposition:image/webp:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_WEBP_INLINE',
-  }),
-  'attachments:contentDisposition:image/bmp:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_BMP_INLINE',
-  }),
-  'attachments:contentDisposition:image/tiff:inline': defineConfig<boolean>({
-    defaultValue: true, // TIFF is common for scans, often supported by browsers
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_TIFF_INLINE',
-  }),
-  'attachments:contentDisposition:image/x-icon:inline': defineConfig<boolean>({ // Favicons
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_X_ICON_INLINE',
-  }),
-  'attachments:contentDisposition:application/pdf:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_PDF_INLINE',
-  }),
-  'attachments:contentDisposition:text/plain:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_TEXT_PLAIN_INLINE',
-  }),
-  'attachments:contentDisposition:video/mp4:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_VIDEO_MP4_INLINE',
-  }),
-  'attachments:contentDisposition:video/webm:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_VIDEO_WEBM_INLINE',
-  }),
-  'attachments:contentDisposition:video/ogg:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_VIDEO_OGG_INLINE',
-  }),
-  'attachments:contentDisposition:audio/mpeg:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_AUDIO_MPEG_INLINE',
-  }),
-  'attachments:contentDisposition:audio/ogg:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_AUDIO_OGG_INLINE',
-  }),
-  'attachments:contentDisposition:audio/wav:inline': defineConfig<boolean>({
-    defaultValue: true,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_AUDIO_WAV_INLINE',
-  }),
-  'attachments:contentDisposition:text/html:inline': defineConfig<boolean>({
-    defaultValue: false, // HTML can contain scripts, always download
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_TEXT_HTML_INLINE',
-  }),
-  'attachments:contentDisposition:text/javascript:inline': defineConfig<boolean>({
-    defaultValue: false, // JS files should always download
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_TEXT_JAVASCRIPT_INLINE',
-  }),
-  'attachments:contentDisposition:application/javascript:inline': defineConfig<boolean>({
-    defaultValue: false, // JS files should always download
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_JAVASCRIPT_INLINE',
-  }),
-  'attachments:contentDisposition:image/svg+xml:inline': defineConfig<boolean>({
-    defaultValue: false, // SVG can contain embedded scripts and vulnerabilities, default to download. Consider a strong warning if setting to true.
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_IMAGE_SVG_XML_INLINE',
-  }),
-  'attachments:contentDisposition:application/xml:inline': defineConfig<boolean>({
-    defaultValue: false, // XML can have XSLT for scripts, default to download
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_XML_INLINE',
-  }),
-  'attachments:contentDisposition:application/json:inline': defineConfig<boolean>({
-    defaultValue: false, // Often contains sensitive data, better to download unless explicitly intended to display in browser's JSON viewer
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_JSON_INLINE',
-  }),
-  'attachments:contentDisposition:application/x-sh:inline': defineConfig<boolean>({ // Shell scripts
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_X_SH_INLINE',
-  }),
-  'attachments:contentDisposition:application/x-msdownload:inline': defineConfig<boolean>({ // Executables (.exe, .dll, etc.)
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_X_MSDOWNLOAD_INLINE',
-  }),
-  'attachments:contentDisposition:application/octet-stream:inline': defineConfig<boolean>({ // Generic binary data
-    defaultValue: false, // Often used for downloads anyway
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_OCTET_STREAM_INLINE',
-  }),
-  'attachments:contentDisposition:application/msword:inline': defineConfig<boolean>({ // .doc
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_MSWORD_INLINE',
-  }),
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.wordprocessingml.document:inline': defineConfig<boolean>({ // .docx
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_VND_WORDPROCESSINGML_DOCUMENT_INLINE',
-  }),
-  'attachments:contentDisposition:application/vnd.ms-excel:inline': defineConfig<boolean>({ // .xls
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_MS_EXCEL_INLINE',
-  }),
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:inline': defineConfig<boolean>({ // .xlsx
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_VND_SPREADSHEETML_SHEET_INLINE',
-  }),
-  'attachments:contentDisposition:application/vnd.ms-powerpoint:inline': defineConfig<boolean>({ // .ppt
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_MS_POWERPOINT_INLINE',
-  }),
-  'attachments:contentDisposition:application/vnd.openxmlformats-officedocument.presentationml.presentation:inline': defineConfig<boolean>({ // .pptx
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_VND_PRESENTATIONML_PRESENTATION_INLINE',
-  }),
-  'attachments:contentDisposition:application/zip:inline': defineConfig<boolean>({
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_ZIP_INLINE',
-  }),
-  'attachments:contentDisposition:application/x-rar-compressed:inline': defineConfig<boolean>({
-    defaultValue: false,
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_APPLICATION_X_RAR_COMPRESSED_INLINE',
-  }),
-  'attachments:contentDisposition:text/csv:inline': defineConfig<boolean>({
-    defaultValue: false, // Best to download for CSV to avoid rendering issues
-    envVarName: 'ATTACHMENTS_CONTENT_DISPOSITION_TEXT_CSV_INLINE',
+  'attachments:contentDisposition:mimeTypeDefaults': defineConfig<Record<string, 'inline' | 'attachment'>>({
+    defaultValue: {
+      // Image Types
+      'image/jpeg': 'inline',
+      'image/png': 'inline',
+      'image/gif': 'inline',
+      'image/webp': 'inline',
+      'image/bmp': 'inline',
+      'image/tiff': 'inline',
+      'image/x-icon': 'inline',
+
+      // Document & Media Types
+      'application/pdf': 'inline',
+      'text/plain': 'inline',
+      'video/mp4': 'inline',
+      'video/webm': 'inline',
+      'video/ogg': 'inline',
+      'audio/mpeg': 'inline',
+      'audio/ogg': 'inline',
+      'audio/wav': 'inline',
+
+      // Potentially Dangerous / Executable / Scriptable Types
+      'text/html': 'attachment',
+      'text/javascript': 'attachment',
+      'application/javascript': 'attachment',
+      'image/svg+xml': 'attachment',
+      'application/xml': 'attachment',
+      'application/json': 'attachment',
+      'application/x-sh': 'attachment',
+      'application/x-msdownload': 'attachment',
+      'application/octet-stream': 'attachment',
+
+      // Other Common Document Formats
+      'application/msword': 'attachment',
+      'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'attachment',
+      'application/vnd.ms-excel': 'attachment',
+      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'attachment',
+      'application/vnd.ms-powerpoint': 'attachment',
+      'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'attachment',
+      'application/zip': 'attachment',
+      'application/x-rar-compressed': 'attachment',
+      'text/csv': 'attachment',
+    },
   }),
 
+
   // Security Settings
   'security:wikiMode': defineConfig<string | undefined>({
     envVarName: 'FORCE_WIKI_MODE',

+ 8 - 19
apps/app/src/server/service/file-uploader/utils/headers.ts

@@ -4,9 +4,6 @@ import type { ExpressHttpHeader, IContentHeaders } from '~/server/interfaces/att
 import type { IAttachmentDocument } from '~/server/models/attachment';
 
 import { configManager } from '../../config-manager';
-import type { ConfigKey } from '../../config-manager/config-definition';
-
-import { DEFAULT_ALLOWLIST_MIME_TYPES, SAFE_INLINE_CONFIGURABLE_MIME_TYPES } from './security';
 
 
 export class ContentHeaders implements IContentHeaders {
@@ -38,32 +35,24 @@ export class ContentHeaders implements IContentHeaders {
     };
 
     const requestedInline = opts?.inline ?? false;
-    const configKey = `attachments:contentDisposition:${actualContentTypeString}:inline` as ConfigKey;
-
-    const rawConfigValue = configManager.getConfig(configKey);
+    const mimeTypeDefaults = configManager.getConfig('attachments:contentDisposition:mimeTypeDefaults') as Record<string, 'inline' | 'attachment'>;
 
     let systemAllowsInline: boolean;
+    const defaultDispositionForType = mimeTypeDefaults[actualContentTypeString];
 
-    const ALL_POSSIBLE_INLINE_MIME_TYPES = new Set<string>([
-      ...DEFAULT_ALLOWLIST_MIME_TYPES,
-      ...SAFE_INLINE_CONFIGURABLE_MIME_TYPES,
-    ]);
-
-    if (!ALL_POSSIBLE_INLINE_MIME_TYPES.has(actualContentTypeString)) {
-      systemAllowsInline = false;
-    }
-    else if (typeof rawConfigValue === 'boolean') {
-      systemAllowsInline = rawConfigValue;
+    if (defaultDispositionForType === 'inline') {
+      systemAllowsInline = true;
     }
     else {
-      systemAllowsInline = DEFAULT_ALLOWLIST_MIME_TYPES.has(actualContentTypeString);
+      systemAllowsInline = false;
     }
 
-    const shouldBeInline = requestedInline && systemAllowsInline;
+    // Determine the final disposition based on user request and system allowance
+    const finalDispositionValue: 'inline' | 'attachment' = (requestedInline && systemAllowsInline) ? 'inline' : 'attachment';
 
     this.contentDisposition = {
       field: 'Content-Disposition',
-      value: shouldBeInline
+      value: finalDispositionValue === 'inline'
         ? 'inline'
         : `attachment;filename*=UTF-8''${encodeURIComponent(filename)}`,
     };

+ 0 - 99
apps/app/src/server/service/file-uploader/utils/security.ts

@@ -1,99 +0,0 @@
-/**
- * Defines default MIME types that are explicitly safe for inline display when served
- * from user uploads. All other types will be forced to download, regardless of
- * their file extension or sniffed content.
- */
-export const DEFAULT_ALLOWLIST_MIME_TYPES = new Set<string>([
-  // Common Image Types (generally safe for inline display)
-  'image/png',
-  'image/jpeg',
-  'image/gif',
-  'image/webp',
-  'image/bmp',
-  'image/tiff',
-  'image/x-icon',
-
-  // Common Audio Types (generally safe for inline display)
-  'audio/mpeg',
-  'audio/ogg',
-  'audio/wav',
-  'audio/aac',
-  'audio/webm',
-
-  // Common Video Types (generally safe for inline display)
-  'video/mp4',
-  'video/webm',
-  'video/ogg',
-
-  // Basic Text (generally safe for inline display)
-  'text/plain',
-  'text/markdown', // Assuming markdown rendering is safe
-]);
-
-/**
- * Defines safe MIME types that can be set to inline by the admin.
- * This set includes types that are generally safe, but might be explicitly forced
- * to 'attachment' by default for security or user experience reasons,
- * and the admin has the option to enable inline display.
- */
-export const SAFE_INLINE_CONFIGURABLE_MIME_TYPES = new Set<string>([
-  // --- Images ---
-  'image/jpeg',
-  'image/png',
-  'image/gif',
-  'image/webp',
-  'image/bmp',
-  'image/tiff',
-  'image/x-icon',
-  'image/svg+xml',
-
-  // --- Audio ---
-  'audio/mpeg',
-  'audio/ogg',
-  'audio/wav',
-  'audio/aac',
-  'audio/webm',
-
-  // --- Video ---
-  'video/mp4',
-  'video/webm',
-  'video/ogg',
-
-  // --- Documents / Text ---
-  'application/pdf',
-  'text/plain',
-  'text/markdown',
-  'text/css',
-  'text/csv',
-  'text/tab-separated-values',
-  'application/xml', // XML can sometimes be rendered inline, but care is needed
-  'application/json',
-
-  // --- Other potentially renderable, but generally safer as attachment by default
-  'application/msword',
-  'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
-  'application/vnd.ms-excel',
-  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
-  'application/vnd.ms-powerpoint',
-  'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
-  'application/zip',
-  'application/x-rar-compressed',
-]);
-
-// Types that are generally NOT safe for inline display and should always default to attachment
-export const NOT_SAFE_INLINE_MIME_TYPES = new Set<string>([
-  'text/html',
-  'text/javascript',
-  'application/javascript',
-  'application/x-sh',
-  'application/x-msdownload',
-  'application/octet-stream',
-]);
-
-// This set is for internal use to define all configurable types for the API and settings.
-// It combines all types that can be handled for disposition settings.
-export const CONFIGURABLE_MIME_TYPES_FOR_DISPOSITION = new Set<string>([
-  ...DEFAULT_ALLOWLIST_MIME_TYPES,
-  ...SAFE_INLINE_CONFIGURABLE_MIME_TYPES,
-  ...NOT_SAFE_INLINE_MIME_TYPES,
-]);