Files
project.linkedcorp.net/backend/src/middleware/upload.js
T
Milton Gonzalez 3d220bcc20 feat: version 2.9.0 -- ver documentos .md/.txt directo, sin descargar
Agrega un boton "Ver" en Documentos (adjuntos de proyectos/actividades) para archivos de
texto plano y Markdown: se leen via blob (mismo mecanismo ya usado para descargar, que ya
maneja el Bearer token) y se muestran en un popup -- el .md se renderiza con formato (marked)
y se sanitiza con DOMPurify antes de insertarse en el DOM, para no abrir una via de XSS con
HTML/JS embebido en un documento subido por otro usuario.

De paso corrige que subir un .md fallaba con "tipo de archivo no permitido": el navegador no
manda un mimetype confiable para esa extension (varia segun SO), asi que ahora se normaliza
por extension en el fileFilter de multer en vez de depender solo del mimetype del navegador.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-07 17:17:03 -05:00

54 lines
1.7 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import multer from 'multer';
import { env } from '../config/env.js';
const ALLOWED_MIME = new Set([
'application/pdf',
'image/png',
'image/jpeg',
'image/webp',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain',
'text/markdown',
'application/zip',
]);
// El navegador no manda un mimetype confiable para .md (varia segun SO: text/markdown,
// application/octet-stream, vacio...) -- se normaliza por extension para no rechazar
// documentos validos ni depender de esa adivinanza.
const MARKDOWN_EXTENSIONS = new Set(['.md', '.markdown']);
const uploadsDir = path.join(env.storageRoot, 'attachments');
fs.mkdirSync(uploadsDir, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, uploadsDir),
// Nombre generado por el servidor: evita path traversal y colisiones.
filename: (req, file, cb) => {
const ext = path.extname(file.originalname).slice(0, 10);
cb(null, `${Date.now()}-${crypto.randomBytes(8).toString('hex')}${ext}`);
},
});
export const upload = multer({
storage,
limits: { fileSize: 15 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (MARKDOWN_EXTENSIONS.has(path.extname(file.originalname).toLowerCase())) {
file.mimetype = 'text/markdown';
return cb(null, true);
}
if (!ALLOWED_MIME.has(file.mimetype)) {
return cb(new Error('Tipo de archivo no permitido'));
}
cb(null, true);
},
});
export { uploadsDir };